Android 事件分发机制和常见问题

0x00 事件分发源码

View 事件分发是 Android 开发者无法避免的问题,针对 Android-10.0.0_r1 中 ViewGroup 和 View 源码,进行一波解读。

在源码的方法中,使用的注释都是//单行注释,文章中将使用/**/多行注释来解读代码。

1. ViewGroupView 源码分析

1.1 ViewGroup.dispatchTouchEvent() 方法
/* 先说明一下,mFirstTouchTarget 会在消费 DOWN 事件后赋值,代码中会指出 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    /* 在 View 中可以看到 mInputEventConsistencyVerifier 的注释,debug 使用,跳过 */
    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);
    }

    /* handled 保存返回结果的变量 */
    boolean handled = false;
    /* onFilterTouchEventForSecurity 出于安全考虑,Window 被遮挡的情况事件不响应 */
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        /* ACTION_MASK 和 ACTION_POINTER_INDEX_MASK 分别表示事件和第几个触摸点 */
        /* 这里是通过与运算获取低 8 位表示的事件类型 */
        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.
            /* 根据注释,出现应用切换、ANR 等事件,可能会没有 CANCEL 或 UP 事件分发,新一轮事件分发会清除掉之前状态
             * 对应代码不再列出来,如其名,下发 cancel 事件并清理 touchtarget */
            cancelAndClearTouchTargets(ev);
            /* 这里也是清除 touchtarget,并且将 FLAG_DISALLOW_INTERCEPT 标识清除,后面会考 */
            resetTouchState();
        }

        // Check for interception.
        /* 拦截相关逻辑 */
        /* 在 DOWN 事件中,或者 touchtarget 不为空的情况下,根据 FLAG_DISALLOW_INTERCEPT 决定是否调用
         * onInterceptTouchEvent,外层 else 逻辑直接查看原注释 */
        /* 后面可以看到,如果消费了 DOWN 事件,mFirstTouchTarget 不会为空 */
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                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;
        }

        // 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.
        /* FLAG_SPLIT_MOTION_EVENTS 这里先不展开,默认为 false */
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        /* 事件正常分发的处理(既不是取消也不拦截) */
        if (!canceled && !intercepted) {

            // If the event is targeting accessibility 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 事件处理 */
            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;
                /* newTouchTarget 还没赋值,逻辑就是判断是否有子 View */
                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.
                    /* 构建子 View 的遍历顺序,有两个属性会影响:
                     * 1. 自定义顺序,setChildrenDrawingOrderEnabled(boolean)
                     * 2. Z 轴顺序 */
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        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;
                        }
                        /* 1. View 不可见或者是关联了动画 2. 事件位置在 View 内 */
                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        /* 如果 TouchTarget 包含当前 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;
                        }

                        /* 清除 UP 事件处理标识 */
                        resetCancelNextUpFlag(child);
                        /* 调用子 View dispatchTouchEvent,子 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();
                            /* 这里是关键,给 mFirstTouchTarget 增加一个节点,之后不为空 */
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                        // The accessibility focus didn't handle the event, so clear
                        // the flag and do a normal dispatch to all children.
                        ev.setTargetAccessibilityFocus(false);
                    }
                    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;
                }
            } /* DOWN 事件分发结束 */
        } /* 非取消和拦截事件的处理 */

        // Dispatch to touch targets.
        /* DOWN 事件的后续事件,都是通过 mFirstTouchTarget 是否为空确定 */
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            /* 调用父类的 dispatchTouchEvent 方法 */
            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;
                    /* 直接向 target 分发事件,如果事件被拦截 分发 CANCEL 事件 */
                    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 View.dispatchTouchEvent()
/**
 * Pass the touch screen motion event down to the target view, or this
 * view if it is the target.
 *
 * @param event The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 */
public boolean dispatchTouchEvent(MotionEvent event) {
    /* 辅助功能,跳过 */
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    /* 对事件序列做一个检查,跳过 */
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        /* ENABLE 状态下被 ScrollBar 消耗 */
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        /* ENABLE 状态下被 TouchListener 消耗 */
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        /* 没有被 ScrollBar 和 TouchListener 消耗的情况下,由 onTouchEvent 处理 */
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    /* 跳过 */
    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    /* 嵌套滑动事件结束,分为 UP、CANCEL、未处理的 DOWN */
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}
1.3 View.onTouchEvent()
/**
 * Implement this method to handle touch screen motion events.
 * <p>
 * If this method is used to detect click actions, it is recommended that
 * the actions be performed by implementing and calling
 * {@link #performClick()}. This will ensure consistent system behavior,
 * including:
 * <ul>
 * <li>obeying click sound preferences
 * <li>dispatching OnClickListener calls
 * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
 * accessibility features are enabled
 * </ul>
 *
 * @param event The motion event.
 * @return True if the event was handled, false otherwise.
 */
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    /* 点击、长按、上下文 */
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    /* DISABLED 状态下,UP 事件中清除 pressed 状态 */
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        /* 重复一遍,DISABLED 状态的可点击 View 依旧会消耗触摸事件,只是不会响应,在这里就直接返回了 */
        return clickable;
    }
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    /* 可点击或者是设置了提示最后返回 true,否则直接返回了 false */
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        /* 具体事件响应,按照 DOWN、MOVE、UP、CANCEL 顺序查看 */
        switch (action) {
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    /* 处理提示 */
                    handleTooltipUp();
                }
                if (!clickable) {
                    /* 不可点击去掉点击提示 */
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                /* UP 事件后续就是处理 prepressed 和 pressed 状态,否则不会有任何处理 */
                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();
                    }

                    /* 这是 pressed 状态 */
                    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 && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check
                        /* UP 事件时,长按还没有响应,移除长按回调 */
                        removeLongPressCallback();

                        /* 点击事件响应,此处位于 pressed 和 prepressed 状态 */
                        /* 在 MOVE 事件中,移出 View 范围,pressed 状态为 false */
                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }

                    /* pressed 状态处理 */
                    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();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                /* 重置长按判断 */
                mHasPerformedLongPress = false;

                if (!clickable) {
                    /* 这里检查长按是判断提示显示,长按也是 clickable */
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    break;
                }

                /* 鼠标操作,跳过 */
                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) {
                    /* 延迟 pressed 状态,并且需要检查长按操作 */
                    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
                    /* 直接设置 pressed 状态,并且设置长按检查 */
                    setPressed(true, x, y);
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                /* 移除状态和回调 */
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;

            case MotionEvent.ACTION_MOVE:
                if (clickable) {
                    /* 设置 Drawable 状态 */
                    drawableHotspotChanged(x, y);
                }

                final int motionClassification = event.getClassification();
                final boolean ambiguousGesture =
                        motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
                int touchSlop = mTouchSlop;
                if (ambiguousGesture && hasPendingLongPressCallback()) {
                    final float ambiguousMultiplier =
                            ViewConfiguration.getAmbiguousGestureMultiplier();
                    if (!pointInView(x, y, touchSlop)) {
                        /* 触摸点不在 View 内,事件具体行为还不确定,延迟长按回应 */
                        // The default action here is to cancel long press. But instead, we
                        // just extend the timeout here, in case the classification
                        // stays ambiguous.
                        removeLongPressCallback();
                        long delay = (long) (ViewConfiguration.getLongPressTimeout()
                                * ambiguousMultiplier);
                        // Subtract the time already spent
                        delay -= event.getEventTime() - event.getDownTime();
                        checkForLongClick(
                                delay,
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    touchSlop *= ambiguousMultiplier;
                }

                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, touchSlop)) {
                    /* 触摸点不在 View 内,移除触摸、长按回调,修改 pressed 状态为 false,UP 事件要用 */
                    // Outside button
                    // Remove any future long press/tap checks
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                }

                final boolean deepPress =
                        motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                if (deepPress && hasPendingLongPressCallback()) {
                    // process the long click action immediately
                    /* DEEP_PRESS 直接响应长按 */
                    removeLongPressCallback();
                    checkForLongClick(
                            0 /* send immediately */,
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                }

                break;
        }

        return true;
    }

    return false;
}

0x01常见问题

如果你有遇到事件相关的(面试)问题,如果一下问题中没有提及,可留言,我将保持持续更新。

1. CANCEL 发生的场景

  1. 事件分发过程中出现了 Window 焦点变化(如启动一个 Activity),会给之前消费事件的 View 发送。
  2. 事件分发过程中,ViewGroup 拦截了消息(指 onInterceptTouchEvent 返回 true),会给之前的 View 发送。
  3. 事件分发过程中,ViewGroup 没有继续往下分发后续事件(不建议这么做,事件分发流程中,需要拦截事件,重写 onInterceptTouchEvent),下一次点击对应 View 会触发。

2. 事件分发过程中移动到 View 外面,会触发哪些事件?

  1. View 消费了 Down 事件,由于 ViewGroup 中会记录 TouchTarget 之后的事件直接分发给 Target,所以 View 可以正常收到 MOVE 和 UP 事件。
  2. View 没有消费 Down 事件,后续事件不会分发到 View,因此移动手指到任何地方都没有关系。

3. 事件分发过程中,移动手指到 View 外,会触发点击事件吗?

  1. 不会,在 View 的 onTouchEvent 方法中,Move 事件会根据当前点击位置是否在 View 内,设置press 状态,Up 事件会根据 press 状态决定是否回调 performClickInternal,此方法里面会回调 listener.onClick()。如果移到 View 外面再移动回 View 最后再离开呢?看代码!

4. 多点触控分开处理

  1. ViewGroup 中存在标志位 FLAG_SPLIT_MOTION_EVENTS,当此标志位为 true 时,可同时点击多个 View。ABC 三个 View 依次点击之后抬起,A 收到三指触摸事件,B 收到两个,C 收到一个

FLAG_SPLIT_MOTION_EVENTS 在 Build.VERSION_CODES.HONEYCOMB(11) 之后在 initViewGroup() 中默认打开,但是会被之后的 initFromAttributes() 默认 false 覆盖。

5. 判断事件是否消费的流程

  1. View 的 dispatchTouchEvent 会先由 OnTouchListener 去处理,没有消费就交给 onTouchEvent
  2. 在 onTouchEvent 中,如果 View 设置了点击、长按、上下文监听之一,即便是 disable 状态也一定会消费掉事件,只是不会执行后续调用逻辑。之后会由 TouchDelegate 处理,之后是判断点击、长按等。

6. requestDisallowInterceptTouchEvent() 作用周期

下一次的 Down 事件中 resetTouchState() 方法会清除掉对应标志。如果在 View 的 Up 事件中设置禁止拦截,实际不会生效。

相关链接

ViewGroup
View

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值