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 | |