Android 事件分发机制
Android 事件分发机制一直让人头痛,之前也是面向 GitHub 编程得过且过。今天下定决心了解一下,以便后面自己定制 View 效果。Android 触摸事件有三个基本类型:ACTION_DOWN
, ACTION_MOVE
, ACTION_UP
,后两者的传递顺序取决于 DOWN
的传递结果,所以就从 ACTION_DOWN
开始分析。
ACTION_DOWN
全景
借用一张下面参考文章里的全景图片,注意这里指的仅仅是 ACTION_DOWN
事件的传递。先解释一下:
- 白色箭头表示事件传递(函数调用)
- 箭头上的标注表示调用前提。(
supper
表示上一级直接调用,false
表示若上级返回false
则系统继续向下调用) - 白色方块内的消费箭头表示若此函数返回对应值,则事件终止传递(也称作被消费了)
以左上角事件入口为例,首先 Activity 收到事件触发 dispatchTouchEvent
,不论返回 true 还是 false 事件均终止,任何组件的任何函数均不会再被调用(包括 activity 自己的 onTouchEvent
),只有 return super.dispatchTouchEvent()
也就是调用了 super 才会继续传递到下一级。
对于下一级 ViewGroup 的 dispatchTouchEvent
来讲,返回 true 同样消费事件立即终止传递。返回 false 则会回溯到上一层的 onTouchEvent
。调用 super 则继续向下传递。
全程传递
我们假设事件没有被拦截、消费,那么整个传输流程类似 U 型:
不难看出,整个流程分为左右两部分,我们暂且叫做分派
与回溯
。分派是自顶到底的,主要用于事件的传递。回溯是从底到顶的,主要用于事件的处理。所有方法的默认实现就是 return super.xxx()
因此事件默认情况下可以走完整个流程。
拦截器
ViewGroup
细心的同学应该注意到了,在分派过程中除了整齐的 dispatchTouchEvent
方法外,乱入了一个 onInterceptEvent
方法,可以称之为拦截器。顾名思义拦截器的作用就是拦截此事件供自己使用(就像大理 gov 一样对待口罩那样)。不难看出 dispatchTouchEvent
方法调用后根据内部处理的不同有三个后果,分别是 ①终止传递 ②向下传递 ③向上回溯,而前面提到过,一般来讲处理具体的处理会放在 onTouchEvent
中。那么问题来了,终止传递后我居然自己无法处理事件?(参考 U 型图中的 ViewGroup 层,无论 dispatchTouchEvent
作何响应都无法调用自己的 onTouchEvent
)
这时候就需要拦截器登场啦。在 onInterceptEvent
方法中若返回 true 则表示拦截此事件,传递给自己的 onTouchEvent
继续处理并决定是否回溯。一定要与 dispatchTouchEvent
区分开,它返回 true 会终止整个流程,更别提回溯了;而拦截器返回 true 后只是停止向下分派,会从自己开始向上回溯。
所以拦截器的作用就是拦截向下分派、自己处理事件并决定是否继续向上回溯。
View
那么 View 怎么办?拦截器方法只有 ViewGroup
中才有,如果 View 想自己处理事件呢?如果理解了拦截器的作用那么这个问题就非常简单了。因为 View 已经是底层组件,它不需要继续向下传递事件,因此它在 onInterceptEvent
直接调用 super 就可以触发自身的 onTouchEvent
。也就是说 View 的 super 实现了 ViewGroup 的拦截器功能。
总结
到此为止 ACTION_DOWN
的传递流程基本上分析完了,最后总结一下在不同的阶段要达到不同的目的应该执行什么操作。
分派阶段
期望行为 | 操作 |
---|---|
继续分派 | 调用 super |
消费事件,终止整个流程 | dispatch return true |
终止分派,向上回溯 | dispatch return false |
终止分派,交给自己处理 | ViewGroup: 拦截器返回 true; View: 调用 super |
回溯阶段
期望行为 | 操作 |
---|---|
继续回溯 | 调用 super 或 return false |
消费事件,终止回溯 | return true |
ACTION_MOVE/UP
不同情况
与 DOWN
事件不同,其他的都属于后续事件,只有消费了它的上一个事件(例如DOWN)的控件,及其上层的控件,才能接受到后续事件。这么说太抽象了,画个图看看(下图中红色表示 DOWN 事件,蓝色表示后续事件)
1. ViewGroup2 dispatchTouchEvent
消费事件(return true
)
此时对于后续事件来讲,ViewGroup2 消费了上一个事件,而 Activity, ViewGroup1 都是它的上层控件,因此他们都能收到后续事件。
2. View onTouchEvent
消费事件
和上一个情形类似,不再赘述了。
3. ViewGroup1 onTouchEvent
消费事件
这次稍微有点不同,因为 ViewGroup1 消费了事件,因此只有它的上层控件才能收到后续事件,也就是只有 Activity 和它自己。
4. View dispatchTouchEvent
返回 false,ViewGroup onTouchEvent
消费事件
这次我们在 View 的 dispatchTouchEvent
返回了 false,也就是会直接回溯到 ViewGroup2. 通过这个案例可以看出,后续事件的传递仅与消费之前事件的控件及其上层控件有关,与之前事件在消费控件下层的传递路径无关。
5. ViewGroup onInterceptEvent
返回 true 拦截事件,ViewGroup onTouchEvent
消费事件
再次印证了上一个情况得出的结论。
总结
总结后续事件的传递路径,就是一直传递到消费前一个事件的控件,并传递到消费前一个事件的方法。注意,onInterceptEvent
只能拦截事件不能消费事件。