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

核心方法

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

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

分发机制

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

下图是分发业务流程图

代码分析

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

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

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

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

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

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

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

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

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

  7. 未设定触摸事件,则执行ButtononTouchEvent 方法,根据 onTouch 方法返回值返回,如果是 true,则 ButtondispatchTouchEvent 方法结束,走 LinearLayout2.2.2 流程

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

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

额外知识

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

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

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