点击事件分发机制 关键源码笔记

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/OneDeveloper/article/details/82943160

请注意,涉及到的源码 SDK 版本为 27,不同版本可能存在偏差,一切以具体的源码为准。

声明: 文字部分主要参考自 《Android 开发艺术探索》,源码部分的解读主要摘抄自 Android 触摸事件机制(三) View中触摸事件详解Android 触摸事件机制(四) ViewGroup中触摸事件详解,但是都加入了自己的思考。


首先,需要明确的就是同一个事件序列,是指从手指接触屏幕的那一刻起到手指离开屏幕的那一刻结束,这个过程所产生的一系列事件,即一个连续的 ACTION_DOWN -> ACTION_MOVE (0 个或者多个)-> ACTION_UP 事件串。

对于 ACTION_DOWNACTION_MOVEACTION_UP ,在下面简称为 DOWNMOVEUP


  1. dispatchTouchEvent (MotionEvent ev)

    用来进行事件分发的,在 View 和 ViewGroup 中(虽然 ViewGroup 继承自 View,但是重写了此方法)的实现会有不同。

    如果事件能够传递给当前 View 或 ViewGroup,则该方法一定会被调用。

    其返回结果表示是否消耗当前事件(消耗的含义是指返回 true,只要返回 true 就表示消耗了,而不管有没有利用事件进行某种逻辑的处理),其受到当前 View/ViewGroup 的 onTouchEvent 和子 View/ViewGroup 的 dispatchTouchEvent 方法的影响。

  2. onInterceptTouchEvent (MotionEvent ev)

    在 ViewGroup 的 dispatchTouchEvent() 方法内部进行调用, 用来判断是否拦截某个事件。在 View 中没有该方法,只存在于 ViewGroup 中。

    如果当前 ViewGroup 拦截了 DOWN 事件则后续的 MOVE、UP 事件传来时都不会调用 onInterceptTouchEvent() 了,如果拦截的是 DOWN 后的 MOVE 事件,那么 UP 事件来的时候还是可能会调用 onInterceptTouchEvent() 方法

  3. onTouchEvent (MotionEvent ev)

    该方法在 View 的 dispatchTouchEvent() 方法内部直接被调用;在 ViewGroup 中是间接被调用 。

    其用来处理事件,返回 true 表示消耗当前事件。

    如果当前 View/ViewGroup 对于传递过来的 DOWN 事件没有消耗,则无法(因为一般情况下是由上级 ViewGroup 主动传递的)接收后续的 MOVE、UP 事件。而如果是没有消耗 MOVE 事件(前提是消耗了 DOWN 事件),则还是可以接收后续的 UP 事件。


提前总结部分:

摘抄自 《Android 开发艺术探索》,但是内容加入了自己的见解。

(1)正常情况下,一个事件序列只能被一个 ViewGroup 拦截且消耗(或者被一个 View 消耗),因为一旦一个元素拦截消耗了某个事件,那么同一个事件序列内的接下来的所有事件都会直接交给它处理。 因为同一个事件序列中的事件不能分别由两个 View 同时处理,但是通过特殊手段可以实现,如一个 View 将本该自己处理的事件通过其他的View 的 onTouchEvent 强行传递给其处理。

(2)在一个事件序列中,某个 ViewGroup 一旦决定拦截该事件序列中的某一事件,那么这一个事件序列之后的事件就只能由它来处理(如果事件序列能够传递给它的话),并且它的 onInterceptTouchEvent() 方法不会再被调用。

(3)某个 View/ViewGroup 如果一旦开始处理事件,如果它不消耗 ACTION_DOWN 事件onTouchEvent 返回了 false),那么同一事件序列中的接下来的事件都不会再交给它来处理了,并且 ACTION_DOWN 事件将重新交由它的父控件处理,即父控件的 onTouchEvent() 会被调用。

(4)如果 View/ViewGroup 不消耗除 ACTION_DOWN 以外的某个事件,那么该事件就会传递到该 View/ViewGroup 就截止了,此时父控件的 onTouchEvent() 并不会被调用(但是该事件会传递给 Activity 处理,因为 Activity 会根据最终返回的 true/false 进行相应的处理),并且当前 View/ViewGroup 可以持续收到后续的事件。

(5)ViewGroup 默认不拦截任何事件。Android 源码中的 ViewGroup 的 onInterceptTouchEvent() 默认返回 false。

(6)View 没有 onInterceptTouchEvent() 方法,一旦事件传给它其 onTouchEvent() 就会被调用。

(7)View 的 onTouchEvent 默认都会消耗事件(返回 true),除非是不可点击的(clickable == false && longClickable == false)。View 的 longClickable 默认为 fasle,而 clickable 则要分情况,比如 Button 的 clickable 默认为 true,而 TextView 则为 false。

(8)View 的 enable 属性不影响 onTouchEvent() 的返回值,即使 View 是 disable 状态的,只要它的 clickable / longClickable 有一个为 true,则 onTouchEvent() 返回 true。

(9)onClick 会发生的前提是当前 View 是可点击的,且接收到了 down 和 up 事件(对于 up 事件接收到了但不一定要消耗,但是对于 down 事件一般情况下不消耗就无法接收后续事件)。更准确的说,是要经过 View 自身的 onTouchEvent() 方法,因为在该方法里面,当传递了 down 和 up 事件之后,就会达到某个条件触发 onClick。(后面的 View 的 onTouchEvent() 的源码分析会说明)

(10)事件传递过程是由外向内的,即事件总是先传递给父元素,再由父元素分发给子元素,通过 requestDisallowInterceptTouchEvent() 方法可以在子元素中干预父元素的事件分发过程,但是 ACTION_DOWN 除外。


源码解读

包含
 - ViewGroup 的 dispatchTouchEvent(MotionEvent ev)
 - ViewGroup 的 dispatchTransformedTouchEvent(MotionEvent ev)
 - View(不含 ViewGroup) 的 dispatchTouchEvent(MotionEvent ev)
 - View 的 onTouchEvent(MotionEvent ev)

  • ViewGroup 的 dispatchTouchEvent(MotionEvent ev) 源码
public boolean dispatchTouchEvent(MotionEvent ev) {
    // mInputEventConsistencyVerifier是调试用的,不会理会
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }
    
    // If the event targets the accessibility focused view and this is it, start
    // normal event dispatch. Maybe a descendant is what will handle the click.
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }
    
    /* 第1步:判断是否要分发该触摸事件 */
    // onFilterTouchEventForSecurity() 表示是否要分发该触摸事件
    // 如果该 View 不是位于顶部,并且有设置属性使该 View 不在顶部时不响应触摸事件,则不分发该触摸事件,即返回false
    // 否则,则对触摸事件进行分发,即返回true
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
        /* 第2步:检测是否需要清空目标和状态 */
        // 如果是 ACTION_DOWN(即按下事件) ,则清空之前的触摸事件处理目标和状态。
        // 这里的情况状态包括:
        // (01) 清空 mFirstTouchTarget 链表,并设置 mFirstTouchTarget 为 null。
        //      mFirstTouchTarget 是"接受触摸事件的 View" 所组成的单链表
        // (02) 清空 mGroupFlags 的 FLAG_DISALLOW_INTERCEPT 标记
        //      如果设置了 FLAG_DISALLOW_INTERCEPT ,则不允许 ViewGroup 对触摸事件进行拦截。
        // (03) 清空 mPrivateFlags 的 PFLAG_CANCEL_NEXT_UP_EVENT 标记
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Throw away all previous state when starting a new touch gesture.
            // The framework may have dropped the up or cancel event for the previous gesture
            // due to an app switch, ANR, or some other state change.
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
        
        /* 第3步:检查当前 ViewGroup 是否想要拦截触摸事件(这里只是单纯的检查是不是要拦截) */
        // 是的话,设置 intercepted 为 true ;否则 intercepted 为 false。
        // 如果是"按下事件(ACTION_DOWN)" 或者 mFirstTouchTarget 不为 null 就执行 if 代码块里面的内容。
        // 否则的话,设置 intercepted 为 true。
        // mFirstTouchTarget != null 表示有子控件接收消耗了 DOWN 事件,因此当 MOVE、UP 事件来的时候能够进入到 if 代码块中
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 检查禁止拦截标记:FLAG_DISALLOW_INTERCEPT
            // 如果调用了 requestDisallowInterceptTouchEvent() 标记的话,则 FLAG_DISALLOW_INTERCEPT 会为 true。
            // 例如,ViewPager在处理触摸事件的时候,就会调用 requestDisallowInterceptTouchEvent(),禁止它的父类对触摸事件进行拦截
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 如果禁止拦截标记为 false 的话,则调用 onInterceptTouchEvent() 并返回拦截状态。
                //如果拦截了 DOWN 则后续的 MOVE、UP 事件时都不会调用 onInterceptTouchEvent() 了
                //如果拦截的是 DOWN 后的 MOVE 事件,那么 UP 事件来的时候还是会调用 onInterceptTouchEvent() 方法
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }
        
        // If intercepted, start normal event dispatch. Also if there is already
        // a view that is handling the gesture, do normal event dispatch.
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }
        
        /* 第4步:检查当前的触摸事件是否被取消 */
        // (01) 对于 ACTION_DOWN 而言,mPrivateFlags 的 PFLAG_CANCEL_NEXT_UP_EVENT 位肯定是 0;因此,canceled=false。
        // (02) 当前的 View/ViewGroup 要被从父View中 detach 时, PFLAG_CANCEL_NEXT_UP_EVENT 就会被设为 true;
        //      此时,它就不再接受触摸事情。
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;
                
        /* 第5步:将触摸事件分发给"当前 ViewGroup 的 子 View/ViewGroup" */
        // 如果触摸"没有被取消",同时也"没有被拦截"的话,则将触摸事件分发给它的子View和子ViewGroup。
        // 如果当前 ViewGroup 的孩子能接受触摸事件的话,则将该孩子添加到 mFirstTouchTarget 链表中。
        // Update list of touch targets for pointer down, if needed.
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {
            // If the event is targeting accessiiblity focus we give it to the
            // view that has accessibility focus and if it does not handle it
            // we clear the flag and dispatch the event to all children as usual.
            // We are looking up the accessibility focused host to avoid keeping
            // state since these events are very rare.
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;
            //对于 DOWN、MOVE、UP三个事件,只有 DOWN 事件才有可能进入到判断语句中,对子控件进行遍历分发
            //而 MOVE、UP 则是在第6步中,直接遍历 mFirstTouchTarget 链表,查找之前接受 DOWN 事件的孩子,并将触摸事件分配给这些孩子
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                // 这是获取触摸事件的序号 以及 触摸事件的id信息。
                // (01) 对于 ACTION_DOWN,actionIndex 肯定是0
                // (02) 而 getPointerId() 是获取的该触摸事件的id,并将该id信息保存到 idBitsToAssign 中。
                //    这个触摸事件的 id 是为多指触摸而添加的;对于单指触摸,getActionIndex() 返回的肯定是0;
                //    而对于多指触摸,第一个手指的 id 是 0,第二个手指的 id 是1,...依次类推。
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;
                // 清空这个手指之前的 TouchTarget 链表。
                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                removePointersFromTouchTargets(idBitsToAssign);
                // 获取该 ViewGroup 包含的 View/ViewGroup 的数目,
                // 然后递归遍历该 ViewGroup 的孩子,对触摸事件进行分发。
                // 递归遍历 ViewGroup 的孩子:是指对于当前 ViewGroup 的所有孩子,都会逐个遍历,并分发触摸事件;
                //   对于逐个遍历到的每一个孩子,若该孩子是 ViewGroup 类型的话,则会递归到调用该孩子的孩子,...
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
                        // If there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }
                        // 如果 child 可以接受触摸事件,
                        // 并且触摸坐标 (x,y) 在 child 的可视范围之内的话;
                        // 则继续往下执行。否则,调用continue。
                        // child可接受触摸事件:是指child的是可见的(VISIBLE);或者虽然不可见,但是位于动画状态。
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        // getTouchTarget() 的作用是查找 child 是否存在于 mFirstTouchTarget 的单链表中。
                        // (如果是后来有为 ViewGroup 新添加子 View/ViewGroup,则有可能还没有存在于 mFirstTouchTarget 的单链表中,此时就会达到  __标记_1,且如果符合条件就会被新加进 mFirstTouchTarget 的单链表中)
                        // 是的话,返回对应的 TouchTarget 对象(此时就会跳出循环,因为已经找到可以接收 DOWN 事件的子 View/ViewGroup);否则,返回 null。
                        newTouchTarget = getTouchTarget(child);// newTouchTarget 第一次被赋值
                        if (newTouchTarget != null) {
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            //马上跳出循环,会在第 6 步将事件进一步分发给 mFirstTouchTarget 中的子 View/Group
                            break;
                        }
                        // 重置 child的mPrivateFlags 变量中的 PFLAG_CANCEL_NEXT_UP_EVENT 位。
                        resetCancelNextUpFlag(child);
                        // 调用 dispatchTransformedTouchEvent() 将触摸事件分发给child。 __标记_1
                        // 即 dispatchTransformedTouchEvent() 中会将事件传递给 child 的 dispatchTouchEvent()
                        // 注意,如果进入了 if 语句,则表示 child 消耗了事件,则会在其中将 alreadyDispatchedToNewTouchTarget 置为 true,因此在第 6 步中不会再次对该事件进行分发
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 如果 child 能够接受该触摸事件,即 child 消费或者拦截了该触摸事件的话;
                            // 则调用 addTouchTarget() 将 child 添加到 mFirstTouchTarget 链表的表头,并返回表头对应的 TouchTarget
                            // 同时还设置 alreadyDispatchedToNewTouchTarget 为 true。
                            // 然后跳出循环
                            // 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();
                            //将接受触摸事件的 child 添加到 mFirstTouchTarget 链表的表头
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);// newTouchTarget 第二次被赋值
                            alreadyDispatchedToNewTouchTarget = true;
                            //此时会马上跳出遍历孩子的循环,之后即使还有能够接收 DOWN 事件的子 View/ViewGroup 也不会管了 
                            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();
                }
                // 在 for 循环外
                // 如果 newTouchTarget 为 null(即newTouchTarget第一次被赋值时为null且没有经历第二次赋值),并且 mFirstTouchTarget 不为 null;
                // 则设置 newTouchTarget 为 mFirstTouchTarget 链表中第一个不为空的节点。
                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;
                }
            }
        }
        
        /* 第6步:进一步的对触摸事件进行分发 */
        // (01) 如果 mFirstTouchTarget 为 null,意味着还没有任何View来接受该触摸事件;
        //   此时,将当前 ViewGroup 看作一个 View;
        //   将会调用"当前的 ViewGroup 的父类 View 的 dispatchTouchEvent() "对触摸事件进行分发处理。
        // (02) 如果mFirstTouchTarget 不为 null,意味着 ViewGroup 的子 View/ViewGroup 中
        //   有可以接受触摸事件的。那么,就将触摸事件分发给这些可以接受触摸事件的子 View/ViewGroup。
        if (mFirstTouchTarget == null) {
            // 注意:这里的第3个参数是 null
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            //只要 mFirstTouchTarget 不 null,就一定会经过这一步,但是也会根据第 5 步的执行的结果来决定之后逻辑(因为第 5 步中只 break、continue 掉 for 循环,并没有直接 return )
            // 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;
            // 这里会遍历 mFirstTouchTarget 中所有的子 View,把当前事件对其中的所有子 View 
            // 都分发一次,但是对于 DOWN—MOVE-UP 这系列事件来说 mFirstTouchTarget 中
            // 的子 View 一般情况下都只有一个,存在多个的情况一般是针对于多点触控来说的,
            // 因为只有在第 5 步时对应的那三种事件才有可能给 mFirstTouchTarget 添加子 View
            // 且每个 DOWN 事件来的时候 mFirstTouchTarget 都会被重置
            // 而且在分发给所有子 View 的时候,只要其中有一个子 View 消耗了当前事件
            // handled 就会恒为 true,且不会受之后其它未消耗事件的子 View 的影响
            while (target != null) {
                final TouchTarget next = target.next;
                // 对于 ACTION_DOWN 事件来说的,如果进入到了第 5 步里面的逻辑,则会
				// 将消耗了 DOWN 事件的子 View 添加到 mFirstTouchTarget,那个时候会触发子
				// View 的 onTouchEvent(),因此在这里无须重复触发了。
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    //(1) 当 cancelChild == true 此时在
                    // dispatchTransformedTouchEvent() 方法内部会给 child 分发 ACTION_CANCEL 事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;// 如果前面拦截了当前事件,则 intercepted 为 true
                    // 符合条件,handled 就会被置为 true,且不会再被该为 false
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    //(2) 当 cancelChild 为 true 时,就会把子 View/ViewGroup 从原本的 mFirstTouchTarget 的单链表中剔除掉,
                    //	  所以之后该子 View/ViewGroup 就无法再接收后续事件了
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                    
					// 如果 cancelChild 一直为 true,则整个循环下来,会把 mFirstTouchTarget 清空;
					// 而如果某一个或者数个 View/ViiewGroup 对应的 cancelChild 为 true,则只会把这
					// 几个从 mFirstTouchTarget 中移除
                }
                predecessor = target;
                target = next;
            }
        }
        
        /* 第7步:再次检查取消标记,并进行相应的处理 */
        // Update list of touch targets for pointer up or cancel, if needed.
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }
    
    // mInputEventConsistencyVerifier是调试用的,不会理会
    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

  • ViewGroup 的 dispatchTransformedTouchEvent(MotionEvent ev) 源码
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;
    
    // 检测是否需要发送 ACTION_CANCEL。(这里不是针对 DOWN、MOVE、UP 事件进行分发)
    // 如果 cancel 为 true 或者 action 是 ACTION_CANCEL;      // 则设置消息为 ACTION_CANCEL,并将 ACTION_CANCEL 消息分发给对应的对象,并返回。
    // (01) 如果 child 是空,则将 ACTION_CANCEL 消息分发给当前 ViewGroup;
    //      只不过会将 ViewGroup 看作它的父类 View,调用 View 的 dispatchTouchEvent() 接口。
    // (02) 如果 child 不是空,调用 child的dispatchTouchEvent()。
    final int oldAction = event.getAction();
    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;
    }
    
    // 计算触摸事件的id信息
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    
    // 如果新的id信息为0,则返回false。
    if (newPointerIdBits == 0) {
        return false;
    }

	// 如果计算得到的前后触摸事件id信息相同,则执行不需要重新计算MotionEvent,直接执行if语句块进行消费分发;
    // 否则,就重新计算MotionEvent之后,再进行消息分发。
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            // 这里才是正常的对 DOWN、MOVE、UP 事件进行分发
            // (01) 如果 child 是空,则将 ViewGroup 看作它的父类 View,调用 View 的 dispatchTouchEvent() 接口。
            // (02) 如果 child 不是空,调用 child 的 dispatchTouchEvent()。
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                handled = child.dispatchTouchEvent(event);
                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }
    
    // 这里也是正常的对 DOWN、MOVE、UP 事件进行分发
    // (01) 如果 child 是空,则将 ViewGroup 看作它的父类 View,调用 View 的 dispatchTouchEvent() 接口。
    // (02) 如果 child 不是空,调用 child的dispatchTouchEvent()。
    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());
        }
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    // Done.
    transformedEvent.recycle();
    return handled;
}

  • View(不含 ViewGroup) 的 dispatchTouchEvent(MotionEvent ev) 源码

只包含关键部分

public boolean dispatchTouchEvent(MotionEvent event) {
	// 调试用的,这里不用理会
 	if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }
    ...
    boolean result = false;
    ...
     // 如果该 View 被遮蔽,并且该 View 在被遮蔽时不响应点击事件;
    // 此时,返回 false;不会执行 onTouch() 或 onTouchEvent(),即过滤调用该点击事件。
    // 否则,返回 true。
    // 被遮蔽的意思是:该View不是位于顶部,有其他的View在它之上。
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        // 如果符合条件,会先调用 OnTouchListener 的 onTouch()
        // 即通过 View#setOnTouchListener() 设置的 OnTouchListener
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        
        // 如果 onTouch() 返回 false 则会调用 View 自身的 onTouchEvent()
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }
    return result;
}

onFilterTouchEventForSecurity() 表示是否要分发该触摸事件;如果该 View 不是位于顶部,并且有设置属性使该 View 不在顶部时不响应触摸事件,则不分发该触摸事件。

从上面可以看到,如果事件到达了 View#dispatchTouchEvent(),则正常情况下的优先级为 View#setOnTouchListener() 的 onTouch() -> View#onTouchEvent() ,且 OnTouchListener#onTouch() 返回 false 才会进一步调用 View#onTouchEvent()


  • View 的 onTouchEvent(MotionEvent ev) 源码
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return clickable;
    }
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

	// 只要 CLICKABLE 和 LONG_CLICKABLE 有一个为 true,就会使 onTouchEvent() 返回 true
	// 而不管是不是 DISABLE 状态
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    handleTooltipUp();
                }
                if (!clickable) {
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
                    if (prepressed) {
                        // The button is being released before we actually
                        // showed it as pressed.  Make it show the pressed
                        // state now (before scheduling the click) to ensure
                        // the user sees it.
                        setPressed(true, x, y);
                    }
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check
                        removeLongPressCallback();
                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            // 如果符合条件,就会触发 performClick(),该方法里面会调用 OnClickLsitener 的 onClick()
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;
            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                // 设置的值会影响到 “case MotionEvent.ACTION_UP” 中能否触发 performClick()
                mHasPerformedLongPress = false;
                if (!clickable) {
                    checkForLongClick(0, x, y);
                    break;
                }
                if (performButtonActionOnTouchDown(event)) {
                    break;
                }
                // Walk up the hierarchy to determine if we're inside a scrolling container.
                boolean isInScrollingContainer = isInScrollingContainer();
                // For views inside a scrolling container, delay the pressed feedback for
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);
                    checkForLongClick(0, x, y);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;
            case MotionEvent.ACTION_MOVE:
                if (clickable) {
                    drawableHotspotChanged(x, y);
                }
                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    // Remove any future long press/tap checks
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                }
                break;
        }
        return true;
    }
    return false;
}

注意到,当处理 case MotionEvent.ACTION_DOWN 时,会触发 mHasPerformedLongPress = false; ,而 mHasPerformedLongPress 的值,会影响到 case MotionEvent.ACTION_UP 中对于 performClick(); 的执行。

从上面可以知道,通过 View#setOnClickListener() 设置的 onClick() 回调的优先级最低,且需要 UP 事件能够传递到当前 View 的 onTouchEvent() 中(隐含条件即消耗了 DOWN 事件)。

这里就涉及到前面总结部分的第(9)小点。


彩蛋部分:

1、如果子控件消耗了 DOWN 事件,但是父 ViewGroup 拦截了 MOVE 事件,之后会发生什么样的事情?

此时子控件会接收到一个 ACTION_CANCEL 事件(在 ViewGroup 的 dispatchTouchEvent() 源码分析中的第 6 步的 while 循环部分可以得到验证;且如果 MOVE 事件被拦截,那么 intercepted 为 true 且 newTouchTarget == null 且 target != null,这里是由于整体的逻辑确定的),之后的 UP 事件也不会再传递给该子控件了,然后该 MOVE 事件就消失了(最终会给 Activity,因为 Activity 会根据最终返回的 true/false 进行相应的处理)。(在用自定义控件和手指模拟该事件时,有时候会出现子控件接收了 ACTION_CACEL 事件之后,父控件的 onTouchEvent() 会接受到一个 MOVE 事件,此时需要注意,该 MOVE 事件是由于手指的轻微移动造成的,是事件序列中一个新的 MOVE,而不是之前被拦截的 MOVE 事件。)

2、如果子控件消耗了 DOWN 事件,而没有消耗 MOVE 事件,之后会怎么样?
可以参考总结部分的第(4)点。


补充部分

2019.05.29 补充

所引用源码为 Android API 25

事件最初是传到 Activity 的,到达 Activity#dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    	// 默认空实现,留给开发者的一个方法入口
        onUserInteraction();
    }
    // 通过 Window 将点击事件传递下去
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    // 如果传递下去的点击事件没有被消耗,则传递给 Activity 的 onTouchEvent()
    return onTouchEvent(ev);
}

(1)getWindow().superDispatchTouchEvent(ev)

getWindow() 会得到 PhoneWindow 实例:

// PhoneWindow.java
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

进一步调用 DecorView#superDispatchTouchEvent()

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

而 DecorView 继承自 FrameLayout,因此,当事件到达 DecorView 时,实际上就进入了 ViewGroup 的事件分发流程。

(2)onTouchEvent()

当 getWindow().superDispatchTouchEvent(ev) 无法消耗事件时,即 DecorView 及其子 View/ViewGroup 无法消耗事件,则会将该事件传递给 Activity 的 onTouchEvent()。

// Activity.java
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

Window#shouldCloseOnTouch() 是被隐藏的 API,如 mCloseOnTouchOutside 被设置为 true,且点击事件是否发生在 context 的边界外,且 peekDecorView() 不为 null,则会返回 true。但是一般默认返回 false。

// Window.java
/** @hide */
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
            && isOutOfBounds(context, event) && peekDecorView() != null) {
        return true;
    }
    
    return false;
}

Activity#onTouchEvent() 中,默认情况下,如果 mWindow.shouldCloseOnTouch(this, event) 返回 true,在会导致 Activity finish,否则返回 false,即 Activity 也没有消耗此次事件。

2019.06.04 补充

1、需要记住的一点,当父容器拦截了 DOWN 之后的 MOVE 或者 UP 事件,则对于那一次拦截的事件 event,会被设置为 ACTION_CANCEL 并遍历 mFirstTouchTarget 中的子 View 将该 CANCEL 事件分发给它们,并一次将 mFirstTouchTarget 中的子 View 移除,直到清空 mFirstTouchTarget。

且这次的事件 event 也不会交给父容器的 onTouchEvent() 处理。

(1)如果在 ViewGroup 中,拦截了当前的 MOVE 事件 event1,则后续的 MOVE、 UP 事件则会交由当前 ViewGroup 处理。

但是当前的事件 event1 会被设置为 MotionEvent.ACTION_CANCEL,然后分发 mFirstTouchTarget 链上的所有子 View 去处理,然后依次清除子 View,最终会导致 mFirstTouchTarget 清空。

且事件 event1 不会交由当前 ViewGroup 处理,且后续的事件因为 mFirstTouchTarget 清空了不会再走 onInterceptTouchEvent() 判断方法,也不会被分发给子 View,而是交由自己处理。

(2)如果在 ViewGroup 中,拦截了 UP 事件 event2 ,同 (1)(但是因为是 UP 事件,所以没有后续事件了),并且当前 ViewGroup 也不会接收到 event2 的。

2、但是对于一个 View 来说,如果其能够处理 ACTION_DOWN 事件(即对 DOWN 事件在 onTouchEvent() 中返回 true),那么即使对于 ACTION_MOVE 事件在 onTouchEvent() 中返回 false,正常情况下(即没有父容器以及其他 View 的干预),后续的 ACTION_MOVE、ACTION_UP 都能正常到达 onTouchEvent() 中。

因为能够处理 DOWN 事件,则该 View 会被收集到父容器的 mFirstTouchTarget 中,因此 DOWN 之后的一系列 MOVE 以及 UP 事件都是直接遍历 mFirstTouchTarget 来进行分发的。

3、View 的 onClick 事件能够响应的前提,是 ACTION_UP 事件能够传递到 View 自身的 onTouchEvent() 中,注意,是 View 自身的,而不是传递到其子类的 onTouchEvent() 就行了。或者手动触发其 View#performClick() 方法。

展开阅读全文

没有更多推荐了,返回首页