Android 事件分发机制一直让人头痛,之前也是面向 GitHub 编程得过且过。今天下定决心了解一下,以便后面自己定制 View 效果。Android 触摸事件有三个基本类型:ACTION_DOWN, ACTION_MOVE, ACTION_UP,后两者的传递顺序取决于 DOWN 的传递结果,所以就从 ACTION_DOWN 开始分析。

ACTION_DOWN

全景

 

Android 事件分发机制_android

 

 

借用一张下面参考文章里的全景图片,注意这里指的仅仅是 ACTION_DOWN 事件的传递。先解释一下:

  • 白色箭头表示事件传递(函数调用)
  • 箭头上的标注表示调用前提。(supper 表示上一级直接调用,false 表示若上级返回 false 则系统继续向下调用)
  • 白色方块内的消费箭头表示若此函数返回对应值,则事件终止传递(也称作被消费了)

以左上角事件入口为例,首先 Activity 收到事件触发 dispatchTouchEvent,不论返回 true 还是 false 事件均终止,任何组件的任何函数均不会再被调用(包括 activity 自己的 onTouchEvent),只有 return super.dispatchTouchEvent() 也就是调用了 super 才会继续传递到下一级。

对于下一级 ViewGroup 的 dispatchTouchEvent 来讲,返回 true 同样消费事件立即终止传递。返回 false 则会回溯到上一层的 onTouchEvent。调用 super 则继续向下传递。

全程传递

我们假设事件没有被拦截、消费,那么整个传输流程类似 U 型:

 

Android 事件分发机制_安卓_02

 

 

不难看出,整个流程分为左右两部分,我们暂且叫做分派回溯。分派是自顶到底的,主要用于事件的传递。回溯是从底到顶的,主要用于事件的处理。所有方法的默认实现就是 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 都是它的上层控件,因此他们都能收到后续事件。

 

Android 事件分发机制_java_03

 

 

2. View onTouchEvent 消费事件

和上一个情形类似,不再赘述了。

 

Android 事件分发机制_java_04

3. ViewGroup1 onTouchEvent 消费事件

这次稍微有点不同,因为 ViewGroup1 消费了事件,因此只有它的上层控件才能收到后续事件,也就是只有 Activity 和它自己。

 

Android 事件分发机制_拦截器_05

 

4. View dispatchTouchEvent 返回 false,ViewGroup onTouchEvent 消费事件

这次我们在 View 的 dispatchTouchEvent 返回了 false,也就是会直接回溯到 ViewGroup2. 通过这个案例可以看出,后续事件的传递仅与消费之前事件的控件及其上层控件有关,与之前事件在消费控件下层的传递路径无关。

 

Android 事件分发机制_拦截器_06

5. ViewGroup onInterceptEvent 返回 true 拦截事件,ViewGroup onTouchEvent 消费事件

再次印证了上一个情况得出的结论。

 

Android 事件分发机制_java_07

 

 

总结

总结后续事件的传递路径,就是一直传递到消费前一个事件的控件,并传递到消费前一个事件的方法。注意,onInterceptEvent 只能拦截事件不能消费事件。