阅读 417

View的事件分发(二)源码分析(dispatchTouchEvent)

目录

以下源码基于版本27,并为了方便阅读有所删减。

Activity ---> ViewGroup流程分析

View的事件分发(一)分发流程文章得知,从用户点击屏幕到屏幕中的控件响应操作的大致流程是 Activity-> ViewGroup-> View。那么 Activity 是怎么把事件传递给我们在 xml 中写的根视图呢?就是说 Activity-> ViewGroup 具体是一个怎样的流程?

当一个点击操作发生时,最先传递给 当前 Activity,由 Activity 的 dispatchTouchEvent 方法分发。我们看下 Activity 的 dispatchTouchEvent 的具体源码:

//Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    //调用 window 的 superDispatchTouchEvent 方法进行事件的分发
    //如果返回 true,就代表事件被消费, Activity 的 dispatchTouchEvent 就直接返回 true,不再往下执行。
    //如果返回 false,就代表事件没有被消费,就调用 Activity 的 onTouchEvent 方法
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
复制代码

继续查看 window 的 superDispatchTouchEvent 源码,window 是一个抽象类,有一个唯一的实现类 PhoneWindow(这一点可以在 window 的类注释中查看到),那么我们就找 PhoneWindow 的 superDispatchTouchEvent 方法。

//PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    //mDecor 是 DecorView
    //这里调用 DecorView 的 superDispatchTouchEvent 方法
    return mDecor.superDispatchTouchEvent(event);
}
复制代码

继续查看 DecorView 的 superDispatchTouchEvent 方法

//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
    //调用父类的 dispatchTouchEvent 方法进行分发.
    return super.dispatchTouchEvent(event);
}
复制代码

我们知道可以通过 ((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0); 来找到 Activity 所设置的 View,PhoneWindow 中调用的 mDecor 就是 getWindow().getDecorView() 获取的 DecorView,我们通过 Activity 所设置的 View 就是它的子 View。DecorView 是继承的 FrameLayout,当然 FrameLayout 是没有重写 dispatchTouchEvent 方法的,所以最终就是通过 FrameLayout 的父类 ViewGroup 的 dispatchTouchEvent 方法来将事件分发给我们开发的页面。

总结

完整的事件分发流程:Activity ---> PhoneWindow ---> DecorView ---> ViewGroup ---> ··· --->View

ViewGroup ---> View流程分析

在分析 ViewGroup 的 dispatchTouchEvent 方法之前,先分析一个关键的类:TouchTarget

TouchTarget 是 ViewGroup 中的一个静态内部类。记录了响应 Touch 事件 View 的链表。在 ViewGroup 中,有一个成员变量 private TouchTarget mFirstTouchTarget,这个变量在 dispatchTouchEvent 分发 Touch 事件中起了重要作用。

private static final class TouchTarget {
    //链表的最大长度
    private static final int MAX_RECYCLED = 32;
    //用于控制同步的锁锁
    private static final Object sRecycleLock = new Object[0];
    //内部用于复用的表头
    private static TouchTarget sRecycleBin;
    //可复用的实例链表的长度
    private static int sRecycledCount;

    public static final int ALL_POINTER_IDS = -1; // all ones

    // The touched child view.
    //记录响应 Touch 事件的 View
    public View child;

    // The combined bit mask of pointer ids for all pointers captured by the target.
    // 对目标捕获的所有指针的指针id的组合位掩码,和多点触控有关。
    public int pointerIdBits;

    // The next target in the target list.
    //链表中的下一个记录触摸 View 的 TouchTarget 对象
    public TouchTarget next;

    private TouchTarget() {
    }

    public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
        if (child == null) {
            throw new IllegalArgumentException("child must be non-null");
        }

        final TouchTarget target;
        synchronized (sRecycleLock) {
            if (sRecycleBin == null) {
                //应用中,首次被触摸的时候 sRecycleBin 是为 null 的。创建一个新对象
                target = new TouchTarget();
            } else {
                //将链表的表首的对象赋值为 target,sRecycleBin 指向下一个对象等待下一次复用。
                target = sRecycleBin;
                //表头 sRecycleBin 往下移一位,指向 next 对象。
                sRecycleBin = target.next;
                //可复用的实例链表的长度减一
                sRecycledCount--;
                //将 target 的 next 赋值为 null
                target.next = null;
            }
        }
        //记录响应 Touch 事件的 View。
        target.child = child;
        target.pointerIdBits = pointerIdBits;
        return target;
    }
    //回收 TouchTarget 对象,后面复用
    public void recycle() {
        if (child == null) {
            throw new IllegalStateException("already recycled once");
        }

        synchronized (sRecycleLock) {
            //复用链表的长度最大不超过 MAX_RECYCLED
            if (sRecycledCount < MAX_RECYCLED) {
                //在链表的最前面加一个可复用的对象作为表头。sRecycleBin 指向这个对象。
                next = sRecycleBin;
                sRecycleBin = this;
                sRecycledCount += 1;
            } else {
                next = null;
            }
            child = null;
        }
    }
}
复制代码

mFirstTouchTarget 是通过 TouchTarget.obtain 方法赋值的(这个后面会说),应用首次被触摸的时候是通过 new 一个对象来赋值的,后面触摸的时候,是复用了链表首部对象。最后通过 recycle() 方法用来回收自己,用来以后复用。

首先看 ViewGroup 的 dispatchTouchEvent 方法:

分析之前,我们先确定两个疑问:

  • 问题一:ViewGroup 是怎么分发给子 View 的?
  • 问题二:关于 requestDisallowInterceptTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
    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);
    }
    
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            //取消并清除触摸目标,里面将 mFirstTouchTarget 置为 null。
            // mFirstTouchTarget 是记录响应触摸事件 View 的链表。
            cancelAndClearTouchTargets(ev);
            //如果是 DOWN 事件,就重置触摸状态。
            //将 mGroupFlags 重置 FLAG_DISALLOW_INTERCEPT 标志位,这个是和 requestDisallowInterceptTouchEvent 相关的,后面再进行解释。
            resetTouchState();
            //这两个方法内部都调用了 clearTouchTargets() 方法,回收 mFirstTouchTarget 指向的链表上的对象,将 mFirstTouchTarget 置为null。
        }

①------------        
        //检查是否拦截
        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            //是否不允许拦截
            //(requestDisallowInterceptTouchEvent 就是通过给 mGroupFlags 添加FLAG_DISALLOW_INTERCEPT 标志位来告诉父控件不要拦截的)
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                //如果允许拦截,那就调用 onInterceptTouchEvent 方法查询是否拦截,记录返回值
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                //如果不允许拦截,直接将 intercepted 赋值为 false
                intercepted = false;
            }
        } else {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            //如果不是DOWN 事件,并且 mFirstTouchTarget 为 null,这个 ViewGroup 就继续拦截事件
            //如果能够执行到这里,说明 DOWN 后面的事件传递到了这里,
            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);
        }

        // Check for cancelation.
        //是否自己被拦截,或者传递的是 CANCEL 事件
        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;
        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 事件。
            //(ACTION_POINTER_DOWN 是一个手指按下的后,另一个手指也按下传递的 DOWN 事件)
            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;

                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                removePointersFromTouchTargets(idBitsToAssign);

                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--) {
②------------            
                    //遍历子 View 
                    //注意 这里是倒序遍历的,先看最上面的 View 是否响应操作事件,其实这也是符合我们的直觉的.
                        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;
                        }
③------------            
                        //查看子 View 是否在当前点击区域,如果不在就跳过这次循环
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        //判断这个 View 是否已经在相应事件的链表中。如果在,就跳出循环。
                        //例如:一个手指按在一个按钮上后,另一个手指也按在这个按钮上,这个时候不响应事件。
                        newTouchTarget = getTouchTarget(child);
                        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;
                            break;
                        }

                        resetCancelNextUpFlag(child);
④------------            
                        //这里调用了 dispatchTransformedTouchEvent 方法,
                        //这个方法里面调用子 View 的 dispatchTouchEvent。(后面说明)
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            //如果 dispatchTransformedTouchEvent 返回了 true,就代表这个 child 消费了这个事件
                            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();
⑤------------                 
                            //调用 addTouchTarget 方法,为 mFirstTouchTarget 进行赋值
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }
                //没有找到响应控件的 View,但是 mFirstTouchTarget 不为null(之前存在响应事件的 View)
                //例如:一个手指按在一个按钮上,另个一手指按在其他控件上(没有消费事件)
                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;
                    //遍历链表,将 newTouchTarget 指向链表的尾部的对象。
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
⑥------------
            //如果 mFirstTouchTarget 为null,没有子 View 消费事件
            //就通过 dispatchTransformedTouchEvent 调用自身的 onTouchEvent.
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } 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) {
                    //DOWN 事件传递过来的时候,是走到这里的。这一点在前面的流程中有体现。
                    handled = true;
                } else {
                    // target.child 是否被设置了 CANCEL 事件,或者传递过来的是否是 CANCEL 事件。
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
⑦------------       //对于非 DOWN 事件,也是在这里调用 dispatchTransformedTouchEvent 方法进行分发。                     
                    /*
                     *如果父控件拦截事件 intercepted 为true,则 cancelChild 为 true。
                     *在调用 dispatchTransformedTouchEvent 进行分发事件的时候,传递了一个 CANCEL 事件给子 View(后面说明)
                     */
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
⑧------------                        
                            //如果表头被回收, mFirstTouchTarget 指向下一个对象。
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        //回收对象
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            // UP 事件的时候,重置触摸状态
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}
--------------------------------------------------------------------- 
//ViewGroup 类
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    //创建一个 TouchTarget 对象
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    //将 mFirstTouchTarget 赋值给 target 的 next。第一次赋值的话,mFirstTouchTarget肯定是 null了。
    target.next = mFirstTouchTarget ;
    //mFirstTouchTarget 赋值为 target
    mFirstTouchTarget = target;
    return target;
}
---------------------------------------------------------------------
//ViewGroup 类
private TouchTarget getTouchTarget(@NonNull View child) {
    for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
        //遍历链表,找到该 child 所在的 TouchTarget 对象。
        if (target.child == child) {
            return target;
        }
    }
    return null;
}
---------------------------------------------------------------------
private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    //遍历链表,依次调用链表上TouchTarget对象的 recycle 方法。
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        //将 mFirstTouchTarget 置为null。
        mFirstTouchTarget = null;
    }
}
复制代码

dispatchTransformedTouchEvent 方法:

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

    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        //cancel为 true,或者传递的是一个 CANCEL 事件,设置 cancel 事件
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            //如果 child 为空,就调用父类(View)的 dispatchTouchEvent 方法来分发
            //(里面调用了 onTouchEvent 方法,后面进行分析)
            handled = super.dispatchTouchEvent(event);
        } else {
            //child 不为 null,就调用子类的分发
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    if (newPointerIdBits == 0) {
        return false;
    }

    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                //将事件分发给自己
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                //将事件分发给子 View
                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }

    // 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());
        }

        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}
复制代码
  • 问题一:ViewGroup 是怎么分发给子 View 的?

    DOWN 事件: 通过上述源码分析,ViewGroup 在进行事件分发的时候,先在 DOWN 事件的时候重置一些状态,然后在 代码① 处进行询问是否进行拦截,因为首次传入的事件是 ACTION_DOWN 事件,并且 mFirstTouchTarget 被重置为 null,所以就调用 onInterceptTouchEvent 询问是否拦截。如果 不拦截 就执行到 代码② ,进行遍历子 View,寻找在点击区域的子类(代码③) ,然后通过(代码④) dispatchTransformedTouchEvent 方法调用该 child 的 dispatchTouchEvent 方法进行分发。如此事件就分发到了子 View。

    此时,如果子 View 消费了 DOWN 事件( child 的 dispatchTouchEvent 返回 true ---> dispatchTransformedTouchEvent 方法返回 true)。就执行到了 代码⑤ ,通过 addTouchTarget 将该 View 绑定到 TouchTarget 链表中,并且是在表头位置,并且将 newTouchTarget 和 mFristTouchTarget 都赋值指向表头。如果不消费,就进行下一次循环,寻找消费该事件的子 View。

    那么,如果 ViewGroup 进行事件拦截呢?事件分发该怎么执行?分为两种情况:1. 直接在 DOWN 事件的时候进行拦截;2. 拦截 MOVE、UP 事件。

    1. 如果直接拦截 DOWN 事件,mFirstTouchTarget 为 null,intercepted 为 true。那么直接执行到 代码⑥ ,通过 dispatchTransformedTouchEvent 源码可知,直接执行了super.dispatchTouchEvent(event)。View (ViewGroup 父类是 View)的 dispatchTouchEvent 方法内部调用了 onTouchEvent 方法,从而事件传递到了 ViewGroup 的 onTouchEvent 方法。
    2. MOVE、UP 事件到来的时候开始拦截,此时会产生 CANCEL 事件。具体分析在 延伸知识一:CANCEL 事件产生的内部实现。

    MOVE、UP 事件: MOVE、UP 事件能够传递过来,那就说明有子 View 消费了 DOWN 事件。mFristTouchTarget 不为 null。代码执行到 代码⑦ ,通过 dispatchTransformedTouchEvent 方法将事件分发给消费了 DOWN 事件的子 View。

  • 延伸知识一:CANCEL 事件产生的内部实现。

    CANCEL 事件产生的条件就是,子 View 消费了 DOWN 事件,在 MOVE、UP 事件中,父View 拦截了事件。在 View的事件分发(一)分发流程 中的 ==CANCEL 事件模拟==中看到的日志是:在第一个 MOVE 事件中,子 View 收到了 CANCEL 事件,第二个 MOVE 事件的时候,ViewGroup 的 onTouchEvent 才接收收到事件。 那么我们分析一下流程:

    由于子 View 消费了 DOWN 事件,所以 mFirstTouchTarget 不为 null 。在第一个 MOVE 事件来临的时候,代码执行到了 代码① ,此时 onInterceptTouchEvent 返回 true,所以 intercepted 被赋值为 true。然后代码执行到了 代码⑦ 。intercepted 为true, cancelChild 自然也就为 true。通过 dispatchTransformedTouchEvent 源码可知,cancel 为 true 时,就设置了一个 CANCEL 事件给 child。在 代码⑧ 中,由于此时是单点触摸,mFirstTouchTarget 指向的链表只有绑定这个 View 的TouchTarget对象,next 为 null,所以 mFirstTouchTarget 赋值为 null。如此,child 在父控件拦截的时候就收到了一个 CANCEL 事件。

    第二个 MOVE 事件:由于此时 mFirstTouchTarget 为null,就直接调用了 代码⑥ ,通过 dispatchTransformedTouchEvent 调用了自身的 onTouchEvent 方法。

    也可以看出,一旦 ViewGroup 拦截之后, mFirstTouchTarget 重置为 null,后续事件来的时候直接将 intercepted 设置为 true,而不再调用 onInterceptTouchEvent 方法。

  • 问题二:关于 requestDisallowInterceptTouchEvent

    requestDisallowInterceptTouchEvent 本身是 ViewParent 中的方法,ViewParent 是一个接口,ViewGroup 实现了它。我们子类 View 中通过调用getParent().requestDisallowInterceptTouchEvent(true);来达到请求父控件不拦截。

    //ViewGroup 类
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }
        //给 mGroupFlags 设置标识。
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
    
        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }
    复制代码

    代码块① 中,可以看到先通过 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;来进行判断子类 View 是否设置了不允许拦截标识,然后再对自身是否拦截进行判断。如果不允许拦截将 intercepted 设置为 false。

    在 DOWN 事件中,ViewGroup 会先重置 mGroupFlags 标识。所以并不是说子 View 在请求一次不拦截后,父控件就永远不拦截。

    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        //重置 mGroupFlags
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
    复制代码

View 的 dispatchTouchEvent 方法

//View 类
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            //如果设置了 mOnTouchListener ,并且 onTouch 返回 true
            //就直接 返回结果 true
            result = true;
        }
        //否则,调用 View 的 onTouchEvent 方法
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    return result;
}
复制代码

View 的 onTouchEvent 方法。

public boolean onTouchEvent(MotionEvent event) {
    //------代码省略-------
    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;
        //一个不可用的 View 仍然是可以消费这个事件的,只是没有响应而已。
        //根据 clickable 的值来决定是否消费这个事件。
        return clickable;
    }
    //是否设置了触摸代理 (如果尝试扩大 View 的 touch 范围,可以使用触摸代理)
    if (mTouchDelegate != null) {
        //如果设置了触摸代理,就将事件 event 传递个触摸代理的 onTouchEvent 方法来处理。
        
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    //如果 clickable 为 true ,就执行下面的代码,并返回 true 消费事件。
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                //------代码省略-------
                break;
            case MotionEvent.ACTION_DOWN:
                //------代码省略-------
                break;
            case MotionEvent.ACTION_CANCEL:
                //------代码省略-------
                break;
            case MotionEvent.ACTION_MOVE:
                //------代码省略-------
                break;
                
        }
        return true;
    }
    return false;
}
复制代码

可以看出,View 的 dispatchTouchEvent 中,首先进行判断是否设置了 mOnTouchListener ,如果设置了 mOnTouchListener 并且 mOnTouchListener.onTouch 返回 true,则 View 的 dispatchTouchEvent 方法直接返回 true, 不会调用 View 的 onTouchEvent 方法。否则调用 View 的 onTouchEvent 方法。

在 View 的 onTouchEvent 方法中,我们可以看到如果设置了点击事件和长按事件,那么 onTouchEvent 一定会返回为 true了,也就是消费了事件。最终,事件走到这里就算结束了。

由于 OnLongClickListener 和 OnClickListener 的回调在 onTouchEvent 方法中调用,所以 当我们设置了 setOnTouchListener 并且 onTouch 返回 true 的时候,setOnLongClickListener 和 setOnClickListener 是不会被回调的。

View 的 TouchEvent 详细流程:View的事件分发(三)源码分析

总结

  1. 完整的事件分发流程为:Activity ---> PhoneWindow ---> DecorView ---> ViewGroup ---> View。
  2. ViewGroup 通过倒序遍历子 View 来寻找处理 DOWN 事件的子 View,通过 dispatchTransformedTouchEvent 方法调用 child.dispatchTouchEvent 将事件分发给childView。然后将消费 DOWN 事件的 View 绑定到触摸的链表中。mFirstTouchTarget 指向表头。
  3. ViewGroup 一旦开始拦截事件,如果先前有子 View 处理事件,就分发一个 CANCEL 事件给之前处理事件的 View。之后的事件不在分发给子 View,也不会再调用 onInterceptTouchEvent 方法。
  4. 每一次 DOWN 事件都会重置 mGroupFlags 和 mFirstTouchTarget。子 View 调用 requestDisallowInterceptTouchEvent 方法,作用范围仅限于当前事件流。
  5. 如果设置了 OnTouchListener 并且 onTouch 返回 true,则 OnLongClickListener 和 OnClickListener 的回调不起作用。

本篇文章用于记录学习过程中的理解和笔记,如有错误,请批评指正,万分感谢!

参考文档

《Android开发艺术探索》第三章

Android ViewGroup事件分发机制

View—事件分发