事件的分发(1)-从场景到源码分析事件分发

主目录见:Android高级进阶知识(这是总目录索引)
 看了很多的事件分发文章,感觉都有点欠缺,今天这篇文章将始于场景终于源码。尽量深入地分析事件分发,达到一篇过的效果,即看了这篇不用看第二篇,这里的场景大部分从《可能是讲解Android事件分发最好的文章》来,然后进行用源码进行覆盖,程序员怎么能只记住结论!!!

一.目标

今天的目标也很明确,为了让大家能完全掌握事件分发机制,今天唯一的目标就是利用场景尽量覆盖所有源码,由易到难循序渐进。所以:
1.完全掌握事件分发机制,记住是完全!!!
2.能为后面的实例做准备,所以一定要学好以后才能不迷茫。

二.事件分发

1.基础假设(引用场景文章):

 我们只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL。一个手势(gesture)是一个事件列,以一个DOWN事件开始(当用户触摸屏幕时产生),后跟0个或多个MOVE事件(当用户四处移动手指时产生),最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生)。当我们说到“手势剩余部分”时指的是手势后续的MOVE事件和最后的UP或CANCEL事件。

 在这里我也不考虑多点触摸手势(我们只假设用一个手指)并且忽略多个MOVE事件可以被归为一组这一实际情况。

 我们将要讨论的视图层次是这样的:最外层是一个ViewGroup A,包含一个或多个子view(children),其中一个子view是ViewGroup B,ViewGroupB中又包含一个或多个子view,其中一个子view是 View C,C不是一个ViewGroup。这里我们忽略同层级view之间可能的交叉叠加。


视图层级

假设用户首先触摸到的屏幕上的点是C上的某个点,该点被标记为触摸点(touch point),DOWN事件就在该点产生。然后用户移动手指并最后离开屏幕,此过程中手指是否离开C的区域无关紧要,关键是手势(gesture)是从哪里开始的。

2.场景一

 假设上面的A,B,C都没有覆写默认的事件传播行为,那么下面就是事件传播的过程:
 1.DOWN事件被传递到C,由于C没有监听onTouchListener的onTouch方法,所以会走到C的onTouchEvent事件里面,该方法返回false,表示"我不关心该手势"。
 2.因此该事件被传递到B的onTouchListener中的onTouch方法,因为没有监听该方法,所以会被传递到onTouchEvent里面,该方法返回false,所以表示"我也不关心该手势"。
 3.同样,因为B不关心这个手势,DOWN事件被传到A的onTouchEvent方法中,该方法也返回false。
由于没有view关心这个手势(gesture),它们将不再会从“手势剩余部分”中接收任何事件。

2.1场景一源码分析:

 从场景一我们知道这是一个完整的传递过程。我们首先应该明确所有的事件的源头都是从ViewGroup的dispatchTouchEvent事件开始,也就是首先从ViewGroup开始分发,所以我们从这个方法开始(其他场景分析的源码也会用到这里,这里每个注释都用编号编好,到时我说注释多少多少请对应这里面代码)

  @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
........
            boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
           //1.在这个地方我们事件开始是初始化即清除响应控件和重置触摸状态
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

  
            final boolean intercepted;
           //2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
           //requestDisallowInterceptTouchEvent(false)则会进入拦截事件
           //onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
           //拦截过了intercepted = true
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
           //3.用于记录最新响应控件的
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            //4.如果不是取消和ViewGroup未拦截事件则进入该方法
            if (!canceled && !intercepted) {

                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                //5.如果事件是ACTION_DOWN,ACTION_POINTER_DOWN,ACTION_HOVER_MOVE则进入该判断
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                   //6.如果还未有响应控件且ViewGroup下面有子视图
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        //7.这个ArrayList是对子视图层次进行排序,即明确子视图分发的层级关系
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                         //8.遍历所有的子视图
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            //9.首先从arraylist获取子视图
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                           ........
                         //10.从响应对象链表中查找这个子视图,查找的到就赋值
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                           //11.dispatchTransformedTouchEvent这个方法主要是看
                           //child不为空则调用他的dispatchTouchEvent方法,如果返回true则进入
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                               //12.将这个child添加进响应对象的链表中,这个地方是用头部插入链表的方式,同时赋值mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                //13.如果mFirstTouchTarget 为空则进入这里,这里注意child为空
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
               //14.如果不为空,说明前面的DOWN事件已经响应过了,所以会进入这里
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
.........
          return handled;
    }

这个方法很长,我也是极力省略了,但是这些方法都很重要,不过不用担心,除了代码中的注释,我这里将一一说明。
我们的场景一中的DOWN事件首先就会进入到我们首先看到第一个注释的地方:

//1.在这个地方我们事件开始是初始化即清除响应控件和重置触摸状态
 if (actionMasked == MotionEvent.ACTION_DOWN) {
         cancelAndClearTouchTargets(ev);
          resetTouchState();
   }

如注释说明的因为事件是DOWN,所以说明这是一个新的事件,所以要清除原先响应控件的一些记录,同时重置触摸状态也就是初始化,回到初始状态。然后我们看到下一个关键的地方:

          //2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
           //requestDisallowInterceptTouchEvent(false)则会进入拦截事件
           //onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
           //拦截过了intercepted = true
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

我们的场景一事件传递到这里,因为我们没有覆写默认的事件传播行为,所以我们的onInterceptTouchEvent返回false即不拦截事件,我们的事件就往下传递:

//4.如果不是取消和ViewGroup未拦截事件则进入该方法
if (!canceled && !intercepted) {
}

我们看到事件传递到这边会进行判断,因为canceled为false且我们未拦截所以intercepted也为false,所以这个事件是成立的。会进入到这个判断里面吧:

  //5.如果事件是ACTION_DOWN,ACTION_POINTER_DOWN,ACTION_HOVER_MOVE则进入该判断
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
}

这边我们由于是个或判断,由于我们这个事件就是DOWN,所以我们这个事件也是成立的,我们这个方法也会进入,所以进行到下面这个代码:

 //6.如果还未有响应控件且ViewGroup下面有子视图
if (newTouchTarget == null && childrenCount != 0) {
}

这个判断主要是看newTouchTarget == null 否,因为是DOWN事件,说明这是个新事件还没有响应对象,所以newTouchTarget为空是成立的,然后看childrenCount != 0吗?这个地方我们的视图是不为空的,我们看基础假设那幅图可以知道。所以我们又会进入到这个判断里面:

  //7.这个ArrayList是对子视图层次进行排序,即明确子视图分发的层级关系
 final ArrayList<View> preorderedList = buildTouchDispatchChildList();

这个方法是干嘛的呢?其实从方法名字也可以看出,这个方法是根据层级结构来排序我们的视图,然后加入ArrayList中。这样我们的视图就有先后层级关系了。接着我们就调用来遍历所有的视图:

   //8.遍历所有的子视图
 for (int i = childrenCount - 1; i >= 0; i--) {
}

我们这个地方是对视图层级结构一一做操作首先我们就是:

 //9.首先从arraylist获取子视图
 final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);

可以看出这里获取到这个i对应的子视图,也就是取出我们的第一个视图:

 //10.从响应对象链表中查找这个子视图,查找的到就赋值
 newTouchTarget = getTouchTarget(child);

这个地方是从链表中查找到对应的这个视图,然后赋值给newTouchTarget ,也就说明了我们这个视图将要响应事件了。

 //11.dispatchTransformedTouchEvent这个方法主要是看
//child不为空则调用他的dispatchTouchEvent方法,如果返回true则进入
  if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
}

这个方法很重要,我们看看这个方法干了啥:

  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        
        final int oldAction = event.getAction();
//如果收到的是cancel事件,则直接判断child为空否,如果为空则直接调用ViewGroup父类的dispatchTouchEvent,不然就调用子View的dispatchTouchEvent
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
//省略一些多点触摸的内容
   ..........
        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
           //不为空则调用child的dispatchTouchEvent
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

这个方法看下来刨去细节,其实这个方法是看有没有child,如果有的话直接调用子View的dispatchTouchEvent,这样事件分发就分发下去了,这是一个递归的过程。这里的调用顺序是:根ViewGroup——分发中间ViewGroup——分发到目标View,然后进行回归。所以事件分发到View C的dispatchTouchEvent方法:

 public boolean dispatchTouchEvent(MotionEvent event) {
   .........
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
........
        return result;
    }

我们看到View里面的dispatchTouchEvent()方法并不复杂,首先看li.mOnTouchListener.onTouch(this, event)) 这个方法,这个方法其实就是我们控件addTouchListener监听器时候添加的,如果这个监听里面的onTouch返回true,我们就不调用下面的onTouchEvent()方法了,因为场景一里面是不覆写默认方法的,所以这个li.mOnTouchListener.onTouch(this, event)) 默认是返回false的,所以我们会走到onTouchEvent()方法。这里的onTouchEvent我们先不讲里面的具体内容(这里面在UP事件时候会调用performClick()方法,其实就是调用的onClick方法)。我们知道我们场景一里面视图C的onTouchEvent返回了false,所以会向上询问B,B的onTouchEvent还有C的onTouchEvent都返回了false,所以DOWN事件就没有被消费掉,所以dispatchTransformedTouchEvent()返回为false。所以我们接下来会走到下面这个判断:

  // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                //13.如果mFirstTouchTarget 为空则进入这里,这里注意child为空
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
               //14.如果不为空,说明前面的DOWN事件已经响应过了,所以会进入这里
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

因为我们前边没有一个相应DOWN事件,所以mFirstTouchTarget == null为真,所以我们看注释13处:

   // Dispatch to touch targets.
  if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
} 

即直接调用dispatchTransformedTouchEvent方法且参数child==null。这个方法我们前面看到如果child==null会调用super.dispatchTouchEvent(transformedEvent)即往父类传,表示我不消费这个事件。
所以到这里我们场景一的DOWN就完成了,接下来"剩余手势事件来了"(MOVE,UP,CANCEL),由于不是DOWN事件所以我们的程序会直接跳过注释2,注释5直接来到我们的if (mFirstTouchTarget == null) (注释13,注释14)这个判断,也就是我们会直接调用dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);因为第三个参数child==null,所以我们的事件会直接传给父类,不会再往下传过C,B,A了。也就是说由于没有view关心这个手势(gesture),它们将不再会从“手势剩余部分”中接收任何事件。

3.场景二

 现在,让我们假设C实际上是关心这个手势(gesture)的,原因可能是C被设置成可点击的(clickable)或者你覆写了C的onTouchEvent方法。
 1.DOWN事件被传递给C的onTouchEvent方法,该方法可以做任何它想做的事情,最后返回true。
 2.因为C说它正在处理这个手势(gesture),则DOWN事件将不再被传递给B和A的onTouchEvent方法。
 3.因为C说它正在处理这个手势(gesture),所以“手势剩余部分”的事件也将传递给C的onTouchEvent方法,此时该方法返回true或false都无关紧要了,但是为保持一致最好还是返回true。

3.1场景二源码分析

我们看到相较于第一个场景,这个地方增加了C关心这个手势,也就是说C消费了这个事件,那么我们还是一样,从ViewGroup的dispatchTouchEvent()开始,我们经过初始化然后还是会进入到拦截:

          //2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
           //requestDisallowInterceptTouchEvent(false)则会进入拦截事件
           //onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
           //拦截过了intercepted = true
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

因为同样,我们这个没有拦截即intercepted为false,其他过程条件和场景一相似,所以我们会一步一步到我们的注释11处,也会进行事件分发,然后最终到C的onTouchEvent方法,因为我们知道我们这个场景二里面C的onTouchEvent这个地方返回true,所以注释11处的判断 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 会返回为true。这样我们的流程就会走到注释12处:

//12.将这个child添加进响应对象的链表中,这个地方是用头部插入链表的方式,同时赋值mFirstTouchTarget
  newTouchTarget = addTouchTarget(child, idBitsToAssign);

这样我们就添加我们的响应对象到响应对象链表中,同时赋值mFirstTouchTarget所以我们mFirstTouchTarget就不为null了。然后程序直接break掉,意思就是说这个DOWN事件就不会传递给B和A了,接着我们程序走到注释13处:

   // Dispatch to touch targets.
  if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
} else{
....
}

现在我们这个地方的mFirstTouchTarget == null不为空了,所以我们的流程就会到else程序里面了,我们这里看下else代码里面做了什么:

  // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }

我们看到这里我们会走到 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 这个判断,因为我们这个地方的target==mFirstTouchTarget且newTouchTarget==mFirstTouchTarget且alreadyDispatchedToNewTouchTarget 为true,所以我们这里直接就返回handled=true。到这里我们的DOWN事件也被消费完了,紧接着我们的"剩余手势事件"(MOVE,UP)等也来了。跟DOWN事件一样,我们也会到达注释11处的判断 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))即也会调用View C的onTouchEvent方法,这时候不管onTouchEvent的up,move事件返回true或者false都没有关系,为什么呢?因为这个地方我们已经记录完了mFirstTouchTarget。到这里我们的场景二也已经讲解完毕了。现在我们难度要升级了。

4.场景三

 现在我们将讨论一个新的方法:onInterceptTouchEvent,它只存在于ViewGroup中,普通的View中没有这个方法。在任何一个view的onTouchEvent被调用之前,它的父辈们(ancestors)将先获得拦截这个事件的一次机会,换句话说,它们可以窃取该事件。在刚才的“处理事件”部分中,我们遗漏了这一过程,现在,让我们把它加上:
 1.DOWN事件被传给A的onInterceptTouchEvent,该方法返回false,表示它不想拦截。
 2.DOWN又被传递给B的onInterceptTouchEvent,它也不想拦截,因此该方法也返回false。
 3.现在,DOWN事件被传递到C的onTouchEvent方法,该方法返回true,因为它想处理以该事件为首的手势(gesture)。
现在,该手势的下一个事件MOVE到来了。这个MOVE事件再一次被传递给A的onInterceptTouchEvent方法,该方法再一次返回false,B也同样如此。
然后,MOVE事件被传递给C的onTouchEvent,就像在前一部分中一样。
“手势剩余部分”中其他事件的处理过程和上面一样,假如A和B的onInterceptTouchEvent方法继续返回false的话。

4.1场景三源码分析

因为场景一和场景二我们都没有说明onInterceptTouchEvent这个方法,所以我们场景三说明一下onInterceptTouchEvent这个方法,虽然我们场景三添加进来这个方法,但是我们看到这个地方都是返回的false,所以看到拦截代码:

   //2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
           //requestDisallowInterceptTouchEvent(false)则会进入拦截事件
           //onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
           //拦截过了intercepted = true
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

因为这个地方A,B的onInterceptTouchEvent都返回false跟我们前面场景分析的默认返回false一样。所以代码流程和场景二其实是一样的。要是想要一点不一样,我们就直接来看最后一个场景,也是我认为价值比计较大的一个场景。


大boss

5.场景四

现在,让我们更进一步,假设B没有拦截DOWN事件,但它拦截了接下来的MOVE事件。原因可能是B是一个scrolling view。当用户仅仅在它的区域内点击(tap)时,被点击到的元素应当能处理该点击事件。但是当用户手指移动了一定的距离后,就不能再视该手势(gesture)为点击了——很明显,用户是想scroll。这就是为什么B要接管该手势(gesture)。
下面是事件被处理的顺序:
 1.DOWN事件被依次传到A和B的onInterceptTouchEvent方法中,它们都返回的false,因为它们目前还不想拦截。
 2.DOWN事件传递到C的onTouchEvent方法,返回了true。
在后续到来MOVE事件时,A的onInterceptTouchEvent方法仍然返回false。
 3.B的onInterceptTouchEvent方法收到了该MOVE事件,此时B注意到用户手指移动距离已经超过了一定的threshold(或者称为slop)。因此,B的onInterceptTouchEvent方法决定返回true,从而接管该手势(gesture)后续的处理。
 然后,这个MOVE事件将会被系统变成一个CANCEL事件,这个CANCEL事件将会传递给C的onTouchEvent方法。
现在,又来了一个MOVE事件,它被传递给A的onInterceptTouchEvent方法,A还是不关心该事件,因此onInterceptTouchEvent方法继续返回false。
此时,该MOVE事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。事实上,该MOVE以及“手势剩余部分”都将传递给B的onTouchEvent方法(除非A决定拦截“手势剩余部分”)。
C再也不会收到该手势(gesture)产生的任何事件了。

5.1场景四源码分析

我们看到我们的大boss来了,我们看到这个场景的1中,A和B都不想拦截DOWN所以DOWN事件最终到了视图C,视图C的onTouchEvent返回true表示说我想消费这个事件。所以程序会记录下mFirstTouchTarget为视图C,这个时候MOVE事件来了,到了注释2拦截方法处:

           //2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
           //requestDisallowInterceptTouchEvent(false)则会进入拦截事件
           //onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
           //拦截过了intercepted = true
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
             }

我们看到这时候虽然不是DOWN事件,但是这个时候mFirstTouchTarget != null,所以还是会走进拦截方法,A表示了不想拦截事件,但是B表示你符合我条件我想要拦截你。所以事件就会被B拦截onInterceptTouchEvent返回为true即intercepted为true。所以我们程序到注释4处的时候:

  //4.如果不是取消和ViewGroup未拦截事件则进入该方法
            if (!canceled && !intercepted) {
            }

这个判断就不会通过,即这个过程就不会执行,这样程序就会直接到达注释13和注释14处即:

  // Dispatch to touch targets.
 if (mFirstTouchTarget == null) {
}else{
}

这个时候我们的mFirstTouchTarget 不为空,所以我们就会走到else的代码里面,又到了判断 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) ,这个判断的target是我们的mFirstTouchTarget即视图C,但是我们的newTouchTarget被重新赋值为null了,所以我们这个判断会不成功,就会走到else方法里面即:

   final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }

这段代码里面第一个cancelChild 由于是个或判断,我们知道intercepted被B拦截返回为true了,所以调用dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)这个方法的时候cancelChild为true,到这个dispatchTransformedTouchEvent方法里面的时候我们看看做了啥:

  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        
        final int oldAction = event.getAction();
//如果收到的是cancel事件,则直接判断child为空否,如果为空则直接调用ViewGroup父类的dispatchTouchEvent,不然就调用子View的dispatchTouchEvent
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
//省略一些多点触摸的内容
   ..........
        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
           //不为空则调用child的dispatchTouchEvent
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

我们看到第一个注释处,判断if (cancel || oldAction == MotionEvent.ACTION_CANCEL)这个判断由于传进来的cancel为true,所以我们就设置事件为CANCEL的然后调用给子视图的dispatchTouchEvent即把CANCEL事件循环传给了子视图。也就是场景四里面说的这个MOVE事件将会被系统变成一个CANCEL事件,这个CANCEL事件将会传递给C的onTouchEvent方法。接着我们看上面代码:

      if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }

因为我们的cancelChild为true了,所以代码会走进来这段代码,由于这段代码是循环执行的,所以最终mFirstTouchTarget 会被最终设置为null。
那么如果这时候又来了一个MOVE事件呢?我们的流程又是怎样的?首先我们知道这个MOVE事件也是先到达拦截部分,我们再来回顾下这个方法:

 //2.如果我们的点击事件是ACTION_DOWN或者响应控件不会空且
           //requestDisallowInterceptTouchEvent(false)则会进入拦截事件
           //onInterceptTouchEvent,不然会直接跳过拦截即直接说明已经
           //拦截过了intercepted = true
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

这时候我们再来看这个判断actionMasked == MotionEvent.ACTION_DOWN这个地方肯定是假的,因为我们现在是MOVE事件,mFirstTouchTarget 的话因为上面被设置为null了,所以这个条件肯定就是未假了,我们直接就跳到intercepted = true部分。即不再调用onInterceptTouchEvent方法了。程序又到了注释4的判断if (!canceled && !intercepted) 。由于intercepted为true,所以这个方法被跳过。程序直接会跳到注释13,14处:

 if (mFirstTouchTarget == null) {
.....
}else{
....
}

这个时候我们看到我们的mFirstTouchTarget == null是true的,所以我们代码会调用if判断里面的程序最终调用到 handled = super.dispatchTouchEvent(transformedEvent)方法。这个方法就会调用B里面onTouchEvent方法。也就是场景四最后说的该MOVE以及“手势剩余部分”都将传递给B的onTouchEvent方法(除非A决定拦截“手势剩余部分”)。C再也不会收到该手势(gesture)产生的任何事件了。到这里我们的事件分发已经讲完了。其实过程还是蛮简单的。只要认真阅读源码,结合场景,问题就迎刃而解了。

总结:事件分发看是复杂,那是因为之前大家讲解的都不是很彻底,导致看得人觉得很麻烦,但是只要认真往下走的时候发现还是很清晰的,学会了事件分发,那我们对很多现象就能解释了,接下来期待下一篇事件分发实践吧。

我是分享源码的程序员
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容