View系列->View事件分发

目标

1. 事件分发流程; 
2. onClick执行时机;

关于事件分发, 先只记住流程也可以, 事件分发是从ViewGroup开始执行, 因为View没有子类, 也就没有所谓的事件分发. 或者结合Activity来进行分析, Activity顶层View是DecorView, 而DecorView instanceof ViewGroup, 所以事件分发是从ViewGroup开始.

打算从以下几个方面进行分析:

1、ViewGroup.dispatchTouchEvent与View.dispatchTouchEvent
2、ViewGroup.requestDisallowInterceptTouchEvent
3、ViewGroup.onInterceptTouchEvent
4、View.onTouch
5、View.onTouchEvent
6、View.onLongClick
7、View.onClick
8、ViewGroup.mFirstTouchTarget
9、MotionEvent.ACTION_DOWN、MotionEvent.ACTION_UP之间的关系

在分析之前, 先写几个结论:

  1、mFirstTouchTarget作为是否存在ChildView消费了事件的一个标记, 如果成功消费, 将当前消费MotionEvent的View指向该mFirstTouchTarget, 即mFirstTouchTarget != null, 如果没有View或者存在View但是该View没有消费事件, 则mFirstTouchTarget == null;
  2、MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTOIN_UP三个事件默认情况下, 只有上一个事件执行时onTouchEvent返回true时, 下一个事件才会继续执行.

事件分发的流程可能比较绕, 所以分析完成之后一定要画一个流程图, 切忌死记硬背, 仔细看完之后发现其实可以理解着去记忆事件分发的过程的.

View系列->View事件分发_第1张图片

一、事件分发的起始点ViewGroup.dispatchTouchEvent

1.1 ViewGroup.dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }
    boolean handled = false;
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;
    // Handle an initial down.
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }
    // Check for interception.
    final boolean intercepted;
    // 关于第二个变量mFirstTouchTarget需要结合下文dispatchTransformedTouchEvent才能明白.
    // 1. 触摸事件发生时actionMasked默认为MotionEvent.ACTION_DOWN, 所以此时会直接进入到
    //    if语句内部, 对intercepted进行赋值.
    // 2. 如果在View.onTouchEvent中我们令ACTION_DOWN时返回false, 那么下文就不会触发
    //    addTouchTarget对mFirstTouchTarget进行赋值, 也就是mFirstTouchTarget = null, 
    //    所以当actionMasked == MotionEvent.ACTION_UP时, 由于mFirstTouchTarget = null,
    //    会进入到else中, 然后将intercepted设置为true, 然后直接跳过下文的事件分发操作.
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
        // 首先会获取disallowIntercept, 该值默认为false: 也就是说默认允许进行事件拦截操作,
        // 该变量通过requestDisallowInterceptTouchEvent进行设置.
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            // onInterceptTouchEvent默认返回false, 可以重写该方法然后根据MotionEvent具体
            // 的取值进行返回值的控制.
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            // disallowIntercept为true时, 设置intercepted值为false
            intercepted = false;
        }
    } else {
        intercepted = true;
    }
    // 小结1: 
    // 1. disallowIntercept默认为false, 也就是说允许进行事件拦截操作, 此时再次获取
    //    onInterceptTouchEvent的值.
    // 2. 而disallowIntercept=true时也就是不允许拦截时, 则直接将intercepted置为false.
    // 3. 通过对下文的分析可知, intercepted = false表示进行事件分发操作.
    // 4. 因此可以得出结论, 当允许父View进行事件拦截时, 也就是disallowIntercept=false时, 
    //    通过onInterceptTouchEvent(ev)判断应该如何进行拦截.
    // 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;
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    // 通过这里可以看出intercepted的值的作用, true时跳过if语句不进行事件分发, 
    // false时进入if进行事件分发
    if (!canceled && !intercepted) {
        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;

            removePointersFromTouchTargets(idBitsToAssign);
            final int childrenCount = mChildrenCount;
            // 能进行事件分发的前提条件是当前ViewGroup有ChildView, 如果没有ChildView, 事件分发
            // 也就不存在, 当前View自己消费该MotionEvent.
            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 preorderedList = buildOrderedChildList();
                final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                final View[] children = mChildren;
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                    final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(childIndex);
                    if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                        continue;
                    }
                    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);
                    // 这里进行事件分发操作, 处理childView自己的事件, 返回值这里的用途是?
                    // 返回true表示View自己消费了事件, 如果返回false, 则表示View当前不会消费事件.
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        // 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();
                        // 1. addTouchTarget中会对mFirstTouchTarget进行赋值, 能进入到这里的前提是:
                        //   存在事件分发&&dispatchTransformedTouchEvent返回true.
                        // 2. mFirstTouchTarget指向的是当前获取MotionEvent的View对应的Target.
                        // 3. 现在考虑一个问题dispatchTransformedTouchEvent什么情况会返回true?
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
                }
                if (preorderedList != null) preorderedList.clear();
            }
            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;
            }
        }
    }
    // mFirstTouchTarget = null有以下几种可能:
    // 1. 根据触摸点没有找到合适的ChildView;
    // 2. 找到了合适的ChildView但是该ChildView没有消费该事件;
    // 然后ViewGroup自己消费掉该触摸事件.
    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, 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;
        }
    }
    // 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 (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}
1.2 ViewGroup.dispatchTransformedTouchEvent

   通过MotionEvent找到合适的ChildView, 然后将MotionEvent交给ChildView, ChildView自己去消费该事件, 如果没找到合适的ChildView, 则当前View自己消费该事件

/**
  * Transforms a motion event into the coordinate space of a particular child view,
  * filters out irrelevant pointer ids, and overrides its action if necessary.
  * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
  */
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 (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;
    }
    // 如果child为空, 则当前ViewGroup自己消费该MotionEvent, 以前看代码时有一个困惑点, 如果
    // ViewGroup没有ChildView, 它是如何自己消费MotionEvent的? 当时并没有找到答案.
    // 终于发现是在这里, ViewGroup继承自View, 所以super.dispatchTouchEvent指向的是
    // View.dispatchTouchEvent, 那么事件消费肯定是在View.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());
        }
        // 如果ChildView不为空, 则将MotionEvent交给ChildView自己去处理
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    transformedEvent.recycle();
    return handled;
}

小结:
1、当进行事件分发时, 如果是非MotionEvent.ACTION_CANCEL模式, 首先判断根据触摸点能否获取到ChildView, 如果能获取到ChildView, 则将该MotionEvent交给ChildView自己去处理, 如果没有找到合适的ChildView, 则ViewGroup自己处理该MotionEvent.
2、ViewGroup如何进行事件消费? 由于super指向View, 所以super.dispatchTouchEvent会触发View.dispatchTouchEvent, 因此事件消费必然在View.dispatchTouchEvent中执行. 这里也就是ViewGroup自己消费MotionEvent的入口点

二、View进行事件消费

2.1 View.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    final int actionMasked = event.getActionMasked();
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
                   && (mViewFlags & ENABLED_MASK) == ENABLED
                   && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }
    if (!result && onTouchEvent(event)) {
        result = true;
    }
    return result;
}

小结:在第一部分ViewGroup.dispatchTouchEvent中, mFirstTouchTarget取值取决于ViewGroup.dispatchTransformedTouchEvent的返回值, 而dispatchTransformedTouchEvent内部优势调用了View.dispatchTouchEvent并获取返回值, 所以这里总结dispatchTouchEvent返回true/false的情况;

View系列->View事件分发_第2张图片

结合图和代码, View.dispatchTouchEvent的返回值取决于onTouch与onTouchEvent的返回值, 而onTouchEvent能否执行, 是取决于onTouch的, 这里先假设onTouch为false的情况下, 对onTouchEvent进行分析.

2.2 View.onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        // 控件DISABLED的情况下, 如果控件具备Click能力, 仍然认为控件能自己消费事件.
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }
    // 如果控件不具备Click事件, 直接返回false, 也就是表示View不会消费触摸事件
    if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                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) {
                        removeLongPressCallback();
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                // 所以onClick的执行取决于mHasPerformedLongPress的值, 而mHasPerformedLongPress
                                // 的值取决于onLongClick的返回值, 所以当onLongClick返回false时onClick才会执行.
                                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();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;
                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);
                    // 如果onLongClick返回true, mHasPerformedLongPress = true;
                    // 如果onLongClick返回false, mHasPerformedLongPress = false;
                    checkForLongClick(0);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                drawableHotspotChanged(x, y);
                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();

                        setPressed(false);
                    }
                }
                break;
        }
        // 这里可以看出, 默认情况下, 只要View具备Click或者LongClick能力, 则进入if语句返回true.
        return true;
    }
    // 返回如果不具备click或者longclick能力, 则返回false,
    return false;
}

小结:
1、默认情况下如果View具备Click或者LongClick能力, 则View.onTouchEvent返回true, 同时MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP会依次执行;
2、如果View不具备Click或者LongClick能力, 则View.onTouchEvent返回false;
3、思考一个问题哈, 如果根据MotionEvent不同阶段取值不同, 返回true/false会对事件分发有什么影响吗?

你可能感兴趣的:(View系列->View事件分发)