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

版权声明:转载请标明出处「OneDeveloper」 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 == true)。View 的 longClickable 默认为 fasle,2而 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
                        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;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    //(1) 当 cancelChild == true 此时在 dispatchTransformedTouchEvent() 方法内部会给 child 分发 ACTION_CANCEL 事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    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;
                    }
                    
					//因此,整个循环下来,会把 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()
        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 的 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(); 的执行。

这里就涉及到前面总结部分的第(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)点。

猜你喜欢

转载自blog.csdn.net/OneDeveloper/article/details/82943160