现在的位置: 首页 > 综合 > 正文

ViewGroup中的触摸消息派发dispatchTouchEvent

2017年12月17日 ⁄ 综合 ⁄ 共 9486字 ⁄ 字号 评论关闭
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);//Eng版本中会对输入事件进行一致性检验
        }

        if (DBG_MOTION || DBG_TOUCH) {
            Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent 1: ev = " + ev + ",mFirstTouchTarget = "
                    + mFirstTouchTarget + ",this = " + this);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {//只有这个函数返回true,才会继续执行。这是一个安全拦截策略,默认返回true,开发者可以继承添加自己的安全策略
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            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);//如果是DOWN事件,清楚掉之前的状态,TouchTarget是保存手指点击区域属性的一个类,手指的所有移动过程都会被它记录下来。
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);//ViewGroup拦截下事件进行处理,如果intercepted==true,则事件不再向子视图传递,而是交给ViewGroup中的onTouchEvent处理。如果为false,则事件可以继续往下传递。默认return false。
                    /// M : add log to help debugging       //人工进行拦截
                    if (intercepted == true) {
                        if (DBG_TOUCH) {
                            Xlog.d(TAG, "Touch event was intercepted event = " + ev + ",this = " + this);
                        }
                    }
                    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;//没有touch targets,说明touch targets列表已被清空。如果接下来的事件不是DOWN事件,那么ViewGroup拦截下该事件。(这种条件的满足情况是第一次是DOWN,接下来不是DOWN),这里需要重点关注,因为满足条件就被拦截了
            }

            // Check for cancelation.
            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;
            if (DBG_MOTION) {
                Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent 2: actionMasked = " + actionMasked
                        + ",intercepted = " + intercepted + ",canceled = " + canceled + ",split = "
                        + split + ",mChildrenCount = " + mChildrenCount + ",mFirstTouchTarget = "
                        + mFirstTouchTarget + ",this = " + this);
            }

            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {//事件没有取消且没有被ViewGroup拦截,事件往下分发。
                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 (childrenCount != 0) {
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final View[] children = mChildren;
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);

                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        for (int i = childrenCount - 1; i >= 0; i--) {//从最后添加进来的View开始遍历,当前childView是否可见或是否正在动画且触摸点[x,y]是否落在childView区域中。下面的大段代码所作的工作不过如此
                            final int childIndex = customOrder ?
                                    getChildDrawingOrder(childrenCount, i) : i;
                            final View child = children[childIndex];
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                if (DBG_MOTION) {
                                    Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent continue 6: i = "
                                            + i + ",count = " + childrenCount + ",child = " + child
                                            + ",this = " + this);
                                }
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (DBG_MOTION) {
                                Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent to child 3: child = "
                                        + child + ",childrenCount = " + childrenCount + ",i = " + i
                                        + ",newTouchTarget = " + newTouchTarget + ",idBitsToAssign = " 
                                        + idBitsToAssign + ",mFirstTouchTarget = " + mFirstTouchTarget 
                                        + ",this = " + this);
                            }
                            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);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }

                    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;
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // 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) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,//应该说接下来的事情交给dispatchTransformedTouchEvent处理了
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (DBG_MOTION) {
                            Xlog.d(TAG, "dispatchTouchEvent middle 5: cancelChild = " + cancelChild
                                    + ",mFirstTouchTarget = " + mFirstTouchTarget + ",target = "
                                    + target + ",predecessor = " + predecessor + ",next = " + next
                                    + ",this = " + this);
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                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) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (DBG_MOTION) {
            Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent end 4: handled = " + handled + ",mFirstTouchTarget = "
                    + mFirstTouchTarget + ",this = " + this);
        }

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

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

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (DBG_MOTION) {
            Xlog.d(TAG, "dispatchTransformedTouchEvent 1: event = " + event + ",cancel = "
                    + cancel + ",oldAction = " + oldAction + ",desiredPointerIdBits = "
                    + desiredPointerIdBits + ",mFirstTouchTarget = " + mFirstTouchTarget
                    + ",child = " + child + ",this = " + this);
        }

        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);//ViewGroup.super不就是View吗,因此不管怎样,最后都交给了View.dispatchTouchEvent处理。
            } else {
                handled = child.dispatchTouchEvent(event);//子视图不为空,交给子视图处理,子视图如果是ViewGroup,依然如此,子视图如果是View,则交给View的dispatchTouchEvent处理
            }
            event.setAction(oldAction);
            if (DBG_MOTION) {
                Xlog.d(TAG, "Dispatch cancel action end: handled = " + handled + ",oldAction = "
                        + oldAction + ",child = " + child + ",this = " + this);
            }
            return handled;
        }

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

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            Xlog.i(TAG, "Dispatch transformed touch event without pointers in " + this);
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        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);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                if (DBG_MOTION) {
                    Xlog.d(TAG, "dispatchTransformedTouchEvent 2 to child " + child
                            + ",handled = " + handled + ",mScrollX = " + mScrollX + ",mScrollY = "
                            + mScrollY + ",mFirstTouchTarget = " + mFirstTouchTarget + ",event = "
                            + event + ",this = " + this);
                }
                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);
        }

        if (DBG_MOTION) {
            Xlog.d(TAG, "dispatchTransformedTouchEvent 3 to child " + child + ",handled = "
                    + handled + ",mScrollX = " + mScrollX + ",mScrollY = " + mScrollY
                    + ",mFirstTouchTarget = " + mFirstTouchTarget + ",transformedEvent = "
                    + transformedEvent + ",this = " + this);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

什么样的情况会导致Child不在接收后续ACTION,而收到CANCEL?
        Child收到TOUCH ACTION后,开始做自己的事,这时,还用户手未松开,即还是一直是ACTION_MOVE,突然,用户的手一不小心移动到该Child边界外,那么这时,Child是永远不会收到ACTION_UP事件了,那么,Child的状态有可能会乱掉(做滚动视图时,会根据MOVE距离来scrollTo / scrollBy View,然而用户手指移动出界,那么,本该是UP时将View复位或是自动滚动到该停止的地方却没有,界面就乱了。因此,Google Android官方解释为:此时的CANCEL
等同于 UP)。

抱歉!评论已关闭.