View的事件体系三(滑动冲突处理)
程序员文章站
2022-06-08 17:57:21
...
一. 滑动冲突场景
-
外部滑动方向和内部滑动方向不一致。
- 举个例子,ViewPager 和 Fragment配合组成的页面滑动效果。左右滑动可以切换页面, 如果页面内又是一个ListView,就会导致滑动冲突,只不过ViewPager内部已经处理了这种滑动冲突,所以我们无需担心这个问题。
- 但是如果我们使用的不是ViewPager,而是ScrollView,那就必须手动处理冲突了。否则,内外两层将只有一层能正常滑动。
内外滑动方向一致。内外两层同时能上下滑动,或同时能左右滑动。
- 前面两种情况的嵌套。
二. 滑动冲突处理的一般规则
- 针对场景1的情况,可以根据起始点的坐标,计算滑动向量Vector(x,y):
- 如果
abs(x) > abs(y)
, 认定为左右滑动 - 如果
abs(x) < abs(y)
, 认定为上下滑动 - x 或 y为正,则滑动方向为右或下
- x 或 y为负,则滑动方向为左或上
- 如果
-
针对场景2的情况,无法根据滑动向量或速度矢量来做判断。此时只能通过界面内容所呈现的业务来做判断:
- 当业务处于某种状态时,外部响应滑动,
- 当业务处于另一种状态时,内部响应滑动。
比场景2更复杂,也只能从业务上寻找判断的点。
三. 解决方式
-
外部拦截法:点击事件都必须先经过父容器的拦截处理,然后判断是否继续分发事件。
-
重写父容器的
onInterceptTouchEvent()
方法,在ACTION_MOVE
的case
分支,判断父容器是否需要处理当前点击事件。@Override public boolean onInterceptTouchEvent(MotionEvent event){ boolean intercept = false; int x = (int) event.getX(); int y = (int) event.getY(); switch(event.getActon()){ case MotionEvent.ACTION_DOWN: intercept = false; break; case MotionEvent.ACTION_MOVE: if(父容器需要处理当前事件){ intercept = true; } else { intercept = false; } break; case MotionEvent.ACTION_UP: intercept = false; break; default: break; } mLastXintercept = x; mLastYintercept = y; return intercept; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
-
-
内部拦截法:首先由子View来处理事件,如果父容器需要处理事件,才会将后续事件交由父容器处理;否则,后续事件将继续由子View来处理。
下面是处理流程:
- 父容器绝不拦截down事件,根据是否需要处理事件,可能会拦截move和up。这是为了保证首先由子元素来处理事件。
- 子元素在分发down时,立即请求父容器禁止拦截事件:
getParent().requestDisallowInterceptTouchEvent(true);
这是为了确保后续事件依然由子容器处理,所以请求父容器禁止拦截事件。 - 子元素在分发move时,如果父容器需要处理事件,则请求父容器允许拦截后续事件(move和up)。
- 如果父容器不需要处理事件,那么父容器将继续保持禁止拦截事件的状态。此时所有的事件都将由子元素来处理。
下面是参考代码:
-
重写子元素的
dispatchTouchEvent(MotionEvent event)
方法://子元素中重写 @Override public boolean dispatchTouchEvent(MotionEvent ev) { int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; int deltaY = y - mLastY; if (父容器需要处理当前事件){ getParent().requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(ev); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
-
重写父容器的
onInterceptTouchEvent
,保证父容器永不拦截down,并一直拦截move和up:@Override public boolean onInterceptTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { return false; } return true; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
显然,如果子元素不请求父容器禁止拦截事件:
getParent().requestDisallowInterceptTouchEvent(false)
,父容器将一直会拦截move和up。
上一篇: 滴滴在网约车内加装防护隔离膜