引子

Android事件分发其实是老生常谈了,但是说实话,我觉得很多人都只是懂其大概,模棱两可。本文的目的就是再次从源码层次梳理一下,重点放在ViewGroup的dispatchTouchEvent方法上,这个方法是事件分发的核心中的核心!我们借此以小见大,理解事件分发的机制。ps,本文着重在源码和分析,就不怎么画图了(其实是懒),大家可以看网上相关图片,随便一搜很多。

先简单讲一下事件分发的源头

很多人讲事件分发,都说其开始是从Activity的dispatchTouchEvent开始的,大家可以简单这么理解,但是肯定会有人疑问,Activity的这个方法从哪儿调用的呢?我写了一个简单的Demo,然后在Activity的dispatchTouchEvent方法里加了一个断点得到其函数调用栈,看下图:

stack.png

好家伙,原来Activity分发之前还有这么多过程,简单梳理了一下:大概是从InputEventReceiver开始,经过ViewRootImpl,里面各种InputStage调用之后,最后给了DecorView,然后DecorView传给的Activity。其实这里挺有意思的,本来DecorView先获取到事件的,但是后来它又分配给了Activity,Activity之后又通过phoneWindow把事件传回给了DecorView,一来一回,就是为了让Activity去处理一下事件而已。Activity传给DecorView之后,DecorView会调用superDispatchTouchEvent方法:

    public boolean superDispatchTouchEvent(MotionEvent event){         return super.dispatchTouchEvent(event);     }

因为DecorView是一个FrameLayout,它最终还是调用了我们熟悉的ViewGroup的dispatchTouchEvent(),这也是本文的主角。所谓的事件分发,本质上就是一个递归函数的调用,这个递归函数就是dispatchTouchEvent,至于onIntercepterTouchEvent,onTouchEvent,OnTouchListener,onClickListener...balabala都是在这个递归函数里面的操作而已,最核心,最骨干的还是dispatchTouchEvent,所以我们来分析它:

ViewGroup的事件分发

大家应该或多或少读过其源码,源码虽然不是太长,但乍一看还是会头大的,我想大多数人可能大概看懂了其逻辑,对于里面很多东西不明所以。比如mFirstTouchTarget是干嘛的?临时变量alreadyDispatchedToNewTouchTarget是干嘛的?里面好像有链表啊,干嘛使的?

这里稍微补充一句,对于事件分发来说,从用户按下到抬起,这是一组事件,以ACTION_DOWN为开头,UP或CANCEL结束。我们后面分析的也是这一组事件。

源码较长,我写了伪代码给大家看看,说是伪代码,其实还是比较全面详细的,省略了部分函数参数,但重点的代码都包含了,重点看注释。如果嫌长,可以直接先看后面的结论,再回头看伪代码。

//本源码来自 api 28,不同版本略有不同。 public boolean dispatchTouchEvent(MotionEvent ev) {     // 第一步:处理拦截    boolean intercepted;        // 注意这个条件,后面会讲    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {     // 子view调用了parent.requestDisallowInterceptTouchEvent干预父布局的拦截,不让它爸拦截它        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;        if (!disallowIntercept) {              intercepted = onInterceptTouchEvent(ev);              ev.setAction(action);           } else {              intercepted = false;          }      } else {         //既不是DOWN事件,mFirstTouchTarget还是null,这种情况挺常见:如果ViewGroup的所有的子View都不消费                //事件,那么当ACTION_MOVE等非DOWN事件到来时,都被拦截了。          intercepted = true;      }      // 第二步,分发ACTION_DOWN     boolean handled = false;     boolean alreadyDispatchedToNewTouchTarget = false; //注意这个变量,会用到    // 不拦截才会分发它,如果拦截了,就不分发ACTION_DOWN了     if (!intercepted) {         //处理DOWN事件,捕获第一个被触摸的mFirstTouchTarget,mFirstTouchTarget很重要,         保存了消费了ACTION_DOWN事件的子view         if (ev.getAction == MotionEvent.ACTION_DOWN) {             //遍历所有子view(看源码知子View是按照Z轴排好序的)             for (int i = childrenCount - 1; i >= 0; i--) {                 //子view如果:1.不包含事件坐标 2. 在动画  则跳过                 if (!isTransformedTouchPointInView() || !canViewReceivePointerEvents()) {                     continue;                 }                 //将事件传递给子view的坐标空间,并且判断该子view是否消费这个触摸事件(分发Down事件)                 if (dispatchTransformedTouchEvent()) {                     //将该view加入头节点,并且赋值给mFirstTouchTarget                     newTouchTarget = addTouchTarget(child, idBitsToAssign);                     alreadyDispatchedToNewTouchTarget = true;                 }              }         }     }          //第三步:分发非DOWN事件         //如果没有子view捕获ACTION_DOWN,则交给本ViewGroup处理这个事件。我们看到,这里并没有判断是否拦截,         //为什么呢?因为如果拦截的话,上面的代码不会执行,就会导致mFirstTouchTarget== null,于是就走下面第一                         //个条件里的逻辑了         if (mFirstTouchTarget == null) {             super.dispatchTouchEvent(ev); //调用View的dispatchTouchEvent,也就是自己处理         } else {             //遍历touchTargets链表,依次分发事件             TouchTarget target = mFirstTouchTarget;             while (target != null) {                 if (alreadyDispatchedToNewTouchTarget) {                   handled = true                 } else {                     if (dispatchTransformedTouchEvent()) {                       handled = true;                     }                   target = target.next;                 }             }         }          //处理ACTION_UP和CANCEL,手指抬起来以后相关变量重置         if (ev.getAction == MotionEvent.ACTION_UP) {