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

栏目: 数据库 · 发布时间: 5年前

内容简介:从当一个点击操作发生时,最先传递给 当前 Activity,由 Activity 的 dispatchTouchEvent 方法分发。我们看下 Activity 的 dispatchTouchEvent 的具体源码:继续查看 window 的 superDispatchTouchEvent 源码,window 是一个抽象类,有一个唯一的实现类 PhoneWindow(这一点可以在 window 的类注释中查看到),那么我们就找 PhoneWindow 的 superDispatchTouchEvent 方法
View的事件分发(二)源码分析
以下源码基于版本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的事件分发(三)源码分析


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

暗趋势

暗趋势

王煜全 / 中信出版集团 / 2019-1 / 59元

《暗趋势》由得到“全球创新260讲”专栏主讲人王煜全,为你揭示藏在科技浪潮中的商业机会,教你获得把握趋势的能力,发现小趋势,抓住大机遇。 《暗趋势》聚焦于改变你生活和未来的产业,深度解读人工智能、混合现实、区块链、生物医疗等你必须关注的科技行业,并分析新科技给企业和个人带来的发展机遇,前瞻性提出企业和个人的思维与行动应对策略。 王煜全作为全球科技前哨侦察兵,以其每年5亿元的科技投资及2......一起来看看 《暗趋势》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具