View 事件分发的那些问题
答案参考自:
View
事件分发机制
答案参考自:
当一个 Touch
事件来到时,首先 Activity
的 dispatchTouchEvent
方法会被触发,如果在 Activity
中没有重写该方法,那么这个方法最终会走到 ViewGroup#dispatchTouchEvent
中进行分发处理:
首先会判断是否已经有子
View
拦截过该方法。如果已有子
View
拦截了该事件,则后续的move
和up
事件都由这个ViewGroup
处理,他的onTouchEvent
被调用。如果该事件没有被拦截过,他就会遍历他下面的子
View
,然后调用子View
的dispatchTouchEvent
方法,若返回了true
,代表该View
处理了事件,并将mFirstTouchTarget
赋值为空,若为false
则继续遍历分发(若还有子View
的话)。若遍历了所有的子
View
,都没有被合适处理,则根View
会自己来处理,Activity
的onTouchEvent
会被触发。
事件是先到 DecorView
还是先到 Window
由上述流程图中可以得知,事件的分发顺序为
Activity -> Window -> DecorView -> ViewGroup -> View
View
的onTouchEvent
、OnClickListerner
和OnTouchListener
的onTouch
方法的三者优先级
答案参考自:
点击事件的执行顺序为
OnTouchListener.DOWN -> OnTouchEvent.DOWN -> OnTouchListener.MOVE -> OnTouchEvent.MOVE -> OnTouchListener.UP -> OnTouchEvent.UP -> OnClickListener
所以三者的优先级为
OnTouchListener > onTouchEvent > onClick
onTouch
和 onTouchEvent
的区别
答案参考自:
onTouchListener
的onTouch
方法优先级比onTouchEvent
高,会先触发。假如
onTouch
方法返回false会接着触发onTouchEvent
,反之onTouchEvent
方法不会被调用。内置诸如
click
事件的实现等等都基于onTouchEvent
,假如onTouch
返回true
,这些事件将不会被触发。
Activity
、ViewGroup
和View
都不消费ACTION_DOWN
,那么ACTION_UP
事件是怎么传递的
首先,如果大家都不消费 ACTION_DOWN,那么 ACTION_DOWN 的事件传递流程是这样的:
1 |
|
接着,由于大家都不消费 ACTION_DOWN
,对于 ACTION_MOVE
和 ACTION_UP
的事件传递是这样的:
1 |
|
点击事件被拦截,但是想传到下面的View
,如何操作
1 |
|
可将点击事件传到下面的View
, 剥夺了父View
对除了ACTION_DOWN
以外的事件的处理权。
如何解决View
的事件冲突
三种出现滑动冲突的情况
内部View
与外部View
的滑动方向相反。内部View
与外部View
的滑动方向相同。- 前两种情况的嵌套。
三种情况的处理思路:
- 通过手势滑动的角度判断滑动的方向。
- 通过当前处于的不同的页面状态来判断应该滑动的
View
。 - 通过前两种的综合使用。
滑动冲突的解决方式
外部拦截法
通过重写父容器的onInterceptTouchEvent
方法,所有的事件都先经过父容器的筛选,对其中父容器需要的事件进行拦截。
1 |
|
内部拦截法
父元素拦截除ACTION_DOWN
以外的其他事件,当事件到达子元素后,由子元素判断是否需要这些事件,不需要的事件将重新交由父容器来处理。这种方法和Android的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent
方法才能正常的工作。
相关阅读链接:Android TouchEvent之requestDisallowInterceptTouchEvent - 简书 (jianshu.com)
子元素的dispatchTouchEvent
方法
1 |
|
父容器的onInterceptTouchEvent
方法:
1 |
|
requestDisallowInterceptTouchEvent
的调用时机
parent.requestDisallowInterceptTouchEvent
的调用需要写在onTouchEvent
方法中
我们一个手势的操作,会经历ACTION_DOWN
、ACTION_MOVE
、ACTION_UP
等操作。
子view
调用requestDisallowInterceptTouchEvent(true)
的时间,是必须在能拿到点击事件的时候。
比如我们在ACTION_DOWN
的时候调用了方法,接下来的ACTION_MOVE
、ACTION_UP
都会直接传递到子view
上了;如果是在子view
的ACTION_MOVE
方法中调用的话,那么要确认父view
在ACTION_MOVE
的过程中,能否将事件传递给子view
就好了。
同时对父 View
和子 View
设置点击方法,优先响应哪个
优先响应子 view。
如果先响应父 view,那么子 view 将永远无法响应。父 view 要优先响应事件,必须先调用 onInterceptTouchEvent 对事件进行拦截,那么事件不会再往下传递,直接交给父 view 的 onTouchEvent 处理。
Android系统中ViewGroup的拦截事件默认不拦截。
ACTION_CANCEL
什么时候触发
如果在父
View
中拦截ACTION_UP
或ACTION_MOVE
,在第一次父视图拦截消息的瞬间,父视图指定子视图不接受后续消息了,同时子视图会收到ACTION_CANCEL
事件。一般是系统自己处理如果触摸某个控件,但是又不是在这个控件的区域上抬起(移动到别的地方了),就会出现
ACTION_CANCEL
。
为什么子 View
不消费 ACTION_DOWN
,之后的所有事件都不会向下传递了
答案是:mFirstTouchTarget
。
当子 view 对事件进行处理的时,那么 mFirstTouchTarget
就会被赋值,若是子 view
不对事件进行处理,那么 mFirstTouchTarget
就为 null
,之后 VIewGroup
就会默认拦截所有的事件。
我们可以从 dispatchTouchEvent
中找到如下代码,可以看出来,若是子 View
不处理 ACTION_DOWN
,那么之后的事件也不会给到它了。
1 |
|
在 ViewGroup
中的 onTouchEvent
中消费 ACTION_DOWN
事件(onInterceptTouch
默认设置),那么 ACTION_MOVE
和 ACTION_UP
事件是怎么传递的?
首先,我们先分析一下 ACTION_DOWN 的事件走向,由于 ViewGroup 中的 onInterceptTouch 是默认设置的,那么 ACTION_DOWN 的事件最终在 ViewGroup 中的 onTouchEvent 方法中停止了,事件走向是这样的:
1 |
|
接着 ACTION_MOVE 和 ACTION_UP 的事件分发流程,之后 onInterceptTouch 和 View 中的方法都不会被调用了,事件分发如下:
1 |
|