Android 事件分发机制一直来说都是一个比较难理解的知识内容,恰逢秋招,花了几天时间学习了一下,终于弄懂了是怎么回事,总结如下:

核心方法

事件分发机制主要有三个方法:

  • dispatchTouchEvent(),主要用于事件分发给子控件
  • onIntercptTouchEvent(),主要用于是否拦截事件,ViewGroup 独有
  • onTouchEvent()方法,主要用于处理触摸事件

分发机制

当屏幕某一个控件被点击时,首先触发顶层布局的dispatchTouchEvent()方法,将事件进行向下传递,如果该布局是ViewGroup,则会先调用onInterceptTouchEvent()方法确定是否拦截该事件,如果返回true,则表示拦截该事件,不再往下传递,并调用自己的 onTouch()方法进行事件处理,否则分发给子控件如此循环。直至有子控件消费此事件。流程图如下,假设目前有三层布局,Activity -> LinearLayout -> Button

下图是分发业务流程图

代码分析

上图我刚开始看的时候也看不懂,下面从代码层面分析,根据具体情况分为多种情况,整个调用过程存在递归,先由上层往下分发事件,然后根据上层根据下层返回的数据进行处理,直至分发过程结束:

  1. Activity 接收到点击事件,首先进行分发,调用dispatchTouchEvent(),在这个函数里面,会进行事件的分发,调用子控件也即LinearLayout的dispatchTouchEvent()方法,根据LinearLayout的dispatchTouchEvent()的返回值进行判定,会有两种结果:

    1. 如果返回true,则表示子控件已经把事件消费掉了,则Activity的 dispatchTouchEvent 方法也返回 true,代表已经处理了这个点击事件,事件分发过程结束。

    2. 如果返回 false,则表示子控件并没有把事件消费掉,则 Activity会调用自己的 onTouchEvent()方法进行时间处理,一般来说我们不会覆写修改 Activity 的 onTouchEvent() 方法,该方法默认返回false,事件分发结束。

  2. 那么在 LinearLayout 的 dispatchTouchEvent() 方法里什么时候会返回 true,什么时候会返回 false 呢?与 Activity 有所不同的的是,LinearLayout 是一个 ViewGroup,多了一个 onInterceptTouchEvent() 方法,LinearLayout 会先调用 onInterceptTouchEvent() 方法决定是否直接拦截事件,会有两种情况:

  3. onInterceptTouchEvent() 返回 true。那么 LinearLayout不会像 Activity 一样无条件向下传递事件,而是直接拦截触摸事件,然后调用自己的 onTouchEvent() 方法,LinearLayout 的dispatchTouchEvent() 方法返回值取决于 onTouchEvent() 的返回值

  4. onInterceptTouchEvent() 返回false,表示 LinearLayout 不拦截事件,会正常往下传递事件,在此例子中将会调用 Button 的 dispatchTouchEvent() 方法。Button 的 dispatchTouchEvent() 方法返回值也有两种:

    1. 返回 true。那么 LinearLayout 的 dispatchTouchEvent() 将直接结束,返回 true,继续走Acticty 的 1.1 流程。
    2. 返回 false。那么 LinearLayout 会调用自己的 onTouchEvent() 方法。和 Activity 类似,一般我们不会覆写这个方法,这个方法默认返回 false,那么接下来继续走 Activity 的1.2流程。
  5. 那么 Button 的 dispatchTouchEvent() 方法里什么时候会返回 true,什么时候会返回 false 呢?与 LinearLayout 不同,Button 不是 ViewGroup 而是 View,只有 dispatchTouchEvent() 和 onTouchEvent 方法,但是 Button 作为 View 比较特殊,还拥有一个方法 onTouch(),Button 的dispatchTouchEvent()方法流程如下。首先检查 Button 是否设定了触摸事件,也即 onTouchListener 是否为空,结果有两种:

  6. 设定了触摸事件,则执行 onToucheListener 的 onTouch 方法,Button 的 dispatchTouchEvent 方法直接返回 true,走 LinearLayout 的 2.2.1 流程

  7. 未设定触摸事件,则执行Button的onTouchEvent 方法,根据 onTouch 方法返回值返回,如果是 true,则 Button 的 dispatchTouchEvent 方法结束,走 LinearLayout 的 2.2.2 流程

使用伪代码的流程图如下:

当然,这个伪代码只是一个非常简略的版本,在实际的源码中,还有很多判定等条件和函数的具体实现,但是大体的流程就这以上这些。

额外知识

此外还有一些其他的知识:

  1. onTouch方法调用时间先于onTouchEvent
  2. 如果子 View不想让父控件拦截事件怎么办?可以在子类中调用requestDisallowInterceptTouchEvent方法来请求事件的直接下发
  3. 如果某控件onTouchEvent方法对DOWN事件返回了 false,也即不对DOWN事件进行处理,那么之后的UP和MOVE事件都不会下发给该控件。这也符合一般常识,如果一个控件对于手指触摸屏幕事件没有反应,那么对于接下来的手指移动和手指抬起的事件也应当无反应。
  4. 从 3 的另一个方向思考,如果某控件的onTouchEvent对于DOWN事件返回了true,也即消费了该事件,那么接下来的事件都会自己处理,不会下发给子控件。
  5. 如果上一次 Button 消费了DOWN事件,而之后来了一个MOVE事件被LinearLayout拦截了,那么会给Button传递一个CANCEL事件

最近秋招大潮来临,各种公司内推电话面,每一次都感觉自己还是个渣,被问了很多不知道的东西,每一次面试都是查缺补漏,加油!预祝自己能找个好工作。