Android事件机制--ViewGroup分发

158 阅读2分钟

ViewGroup事件分发流程图:

ViewGroup分发事件.webp

ViewGroup事件分发源码解析:

ViewGroup.java
    // 触摸目标链表中的第一个目标
    // 什么情况下,才会有多个触摸目标那? 多点触控时,就会形成链.
    private TouchTarget mFirstTouchTarget;
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            // 0001 & 1111 = 0001
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // down事件是整个事件流程(DOWN -> MOVE -> UP)的第一步
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // 重置与清除
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 检测是否拦截
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                // 当child调用 getParent().requestDisallow(),
                // 是否希望过滤父级 viewGroup 的 onInterceptTouchEvent
                // 默认执行 viewGroup.onInterceptTouchEvent()
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) { // 如果允许拦截,则执行拦截方法
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action);
                } else { // child 不希望父view 拦截
                    intercepted = false;
                }
            } else {
                // 假如分发的down事件,child没有处理, 则后续的事件(move->up),child 是没有机会处理的(给你机会,你不珍惜啊~)
                intercepted = true;
            }

            // 是否取消
            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;
            // 没有cancel且不拦截,则向child分发事件
            if (!canceled && !intercepted) {

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

                    // 多点触摸: 一根手指具有唯一的 pointerId, 
                    // actionIndex(可以理解为手指的数量) 却是动态变化的    
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;
                    // 过滤上一个手指的影响
                    removePointersFromTouchTargets(idBitsToAssign);

                    // 查找可处理该事件的child
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // 根据Zorder值 创建 child集合
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();

                        final View[] children = mChildren;
                        // 从后往前遍历childrens. 因为 Zorder 越大,越靠近用户
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // child 不可见且不在手指点击的范围内,则向下查找
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            // 如果已有的触摸目标的 child与之相等,则说明找到了
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            // child得到这次事件的处理权, 调用 view.dispatchTouchEvent
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // child消费了这个事件
                                mLastTouchDownTime = ev.getDownTime();
                                // 查找 child 在 childrens 中的位置
                                if (preorderedList != null) {
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                // 插入触摸链表的头部
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
                    // 如果除第一次触摸到的 child 外,其他的child不消费down事件, 则使用第一个 child
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            if (mFirstTouchTarget == null) {
                // 没有找到消费该事件的 child, 则交给自己处理
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // 分发事件到触摸目标 child
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { // 事件已经消费过了,就不要向下分发了
                        handled = true;
                    } else {
                        // 取消掉吗?
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 事件经过转换后分发给 child处理
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // 如果viewGroup收到cancel或者up事件, 则重置触摸链
            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);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        // 处理cancel事件
        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;
        }

        // 计算 pointerId
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // 我们想要的指针与事件的指针不一样,则放弃
        if (newPointerIdBits == 0) {
            return false;
        }

        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) { // 单点触控下,事件的id与想要的id一致
            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);
                    // 事件原样分发给child
                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else { // 多点触控下, 事件需要经过分割,只包含对应 pointer 的事件
            // 例如; event.action = ACTION_POINTER_UP, 对应child的事件就有两种可能
            // ACTION_UP 和 ACTION_MOVE
            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());
            }
            // 分割转换后的事件分发给 child
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.完成,回收事件
        transformedEvent.recycle();
        return handled;
    }

如何验证触摸目标的数量,就是屏幕上手指的数量

XXActivity.kt
private fun printTouchTarget(view: ViewGroup = findViewById<ParentGroup>(R.id.event_container)) {
    val mFirstTouchTarget = ViewGroup::class.java.getDeclaredField("mFirstTouchTarget")
    mFirstTouchTarget.isAccessible = true
    val firstTouchTargetRef = mFirstTouchTarget.get(view)
    Log.e("XXX", "firstTouchTargetRef :------${firstTouchTargetRef}")
    if (firstTouchTargetRef != null) {
        val innerClass = ViewGroup::class.java.declaredClasses as Array<Class<Any>>
        innerClass.forEach {
            if (it.name == "android.view.ViewGroup$TouchTarget") {
                try {
                    val nextField = it.getDeclaredField("next")
                    var next = nextField.get(firstTouchTargetRef)
                    while (next != null) {
                        Log.e("XXX", "next :------${next}")
                        next = nextField.get(next)
                    }
                }catch(exp: Exception){}
        }
    }
}