Android 事件分发

Android 事件分发

本篇文章主要简单介绍下Android中的事件分发,和大家一起学习,进步,有问题也希望大家及时指证修改.

1: onClick和OnTouch

首先我们在单独的activity中添加个按钮button.增加点击事件setOnClickListener:

java 复制代码
button.setOnClickListener(v -> Log.i(TAG, "onClick: "));

接着添加OnTouch:

java 复制代码
button.setOnTouchListener((v, event) -> {
    Log.i(TAG, "onTouch: " + event.getAction());
    return false;
});

我们执行下点击事件.可以看到输出如下:

2024-04-09 20:54:11.219 17770-17770/? I/testxxx: onTouch: 0 2024-04-09 20:54:11.234 17770-17770/? I/testxxx: onTouch: 2 2024-04-09 20:54:11.294 17770-17770/? I/testxxx: onTouch: 2 2024-04-09 20:54:11.299 17770-17770/? I/testxxx: onTouch: 2 2024-04-09 20:54:11.300 17770-17770/? I/testxxx: onTouch: 1 2024-04-09 20:54:11.306 17770-17770/? I/testxxx: onClick:

OnTouch的事件比较多一些,包含了按下,移动,抬起事件.

事件执行的顺序我们可以看到是OnTouch->onClick.

我们在修改下OnTouch方法:

2024-04-10 10:03:53.245 19437-19437/? I/testxxx: onTouch: 0 2024-04-10 10:03:53.264 19437-19437/? I/testxxx: onTouch: 2 2024-04-10 10:03:53.298 19437-19437/? I/testxxx: onTouch: 2 2024-04-10 10:03:53.304 19437-19437/? I/testxxx: onTouch: 2 2024-04-10 10:03:53.305 19437-19437/? I/testxxx: onTouch: 1

可以看到这里已经没有onCLick事件的执行了.

原因是:onTouch返回true,就代表了该点击事件被onTouch消费掉了,不会继续向下执行. 具体的源码分析可以继续往下看.

2: onInterceptTouchEvent/dispatchTouchEvent/onTouchEvent

首先我们自定义下relationlayout以及button:

java 复制代码
package com.test.touchtest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

/**
 * @Author: zh
 * @Time: 24-4-10.
 * @Describe:
 */
public class CustomLayout extends RelativeLayout {
    private static final String TAG = "CustomLayout";

    public CustomLayout(Context context) {
        super(context);
    }

    public CustomLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(TAG, "onInterceptTouchEvent: " + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent: " + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent: " + event.getAction());
        return super.onTouchEvent(event);
    }
}
java 复制代码
package com.test.touchtest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

/**
 * @Author: zh
 * @Time: 24-4-10.
 * @Describe:
 */
public class CustomButton extends androidx.appcompat.widget.AppCompatButton {
    private static final String TAG = "CustomButton";

    public CustomButton(Context context) {
        super(context);
    }

    public CustomButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent: " + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent: " + event.getAction());
        return super.onTouchEvent(event);
    }
}

执行下button的点击.

2024-04-10 10:16:57.059 25026-25026/com.test.touchtest I/CustomLayout: dispatchTouchEvent: 0 2024-04-10 10:16:57.059 25026-25026/com.test.touchtest I/CustomLayout: onInterceptTouchEvent: 0

2024-04-10 10:16:57.060 25026-25026/com.test.touchtest I/CustomButton: dispatchTouchEvent: 0 2024-04-10 10:16:57.060 25026-25026/com.test.touchtest I/CustomButton: onTouchEvent: 0

2024-04-10 10:16:57.077 25026-25026/com.test.touchtest I/CustomLayout: dispatchTouchEvent: 2 2024-04-10 10:16:57.077 25026-25026/com.test.touchtest I/CustomLayout: onInterceptTouchEvent: 2

2024-04-10 10:16:57.077 25026-25026/com.test.touchtest I/CustomButton: dispatchTouchEvent: 2 2024-04-10 10:16:57.077 25026-25026/com.test.touchtest I/CustomButton: onTouchEvent: 2

2024-04-10 10:16:57.116 25026-25026/com.test.touchtest I/CustomLayout: dispatchTouchEvent: 1 2024-04-10 10:16:57.116 25026-25026/com.test.touchtest I/CustomLayout: onInterceptTouchEvent: 1

2024-04-10 10:16:57.117 25026-25026/com.test.touchtest I/CustomButton: dispatchTouchEvent: 1 2024-04-10 10:16:57.117 25026-25026/com.test.touchtest I/CustomButton: onTouchEvent: 1

2024-04-10 10:16:57.126 25026-25026/com.test.touchtest I/custom: onClick:

可以看到大概的执行顺序如下:

  1. ViewGroup: dispatchTouchEvent
  2. ViewGroup: onInterceptTouchEvent
  3. Button: dispatchTouchEvent
  4. Button: onTouchEvent
  5. Button: onClick

我们在增加下button的setOnToucnListener,打印输出如下:

2024-04-10 11:01:38.824 2160-2160/? I/CustomLayout: dispatchTouchEvent: 0 2024-04-10 11:01:38.825 2160-2160/? I/CustomLayout: onInterceptTouchEvent: 0 2024-04-10 11:01:38.825 2160-2160/? I/CustomButton: dispatchTouchEvent: 0 2024-04-10 11:01:38.826 2160-2160/? I/customActivity: onTouch: 0 2024-04-10 11:01:38.826 2160-2160/? I/CustomButton: onTouchEvent: 0

2024-04-10 11:01:38.843 2160-2160/? I/CustomLayout: dispatchTouchEvent: 2 2024-04-10 11:01:38.843 2160-2160/? I/CustomLayout: onInterceptTouchEvent: 2 2024-04-10 11:01:38.843 2160-2160/? I/CustomButton: dispatchTouchEvent: 2 2024-04-10 11:01:38.843 2160-2160/? I/customActivity: onTouch: 2 2024-04-10 11:01:38.843 2160-2160/? I/CustomButton: onTouchEvent: 2

2024-04-10 11:01:38.902 2160-2160/? I/CustomLayout: dispatchTouchEvent: 1 2024-04-10 11:01:38.902 2160-2160/? I/CustomLayout: onInterceptTouchEvent: 1 2024-04-10 11:01:38.902 2160-2160/? I/CustomButton: dispatchTouchEvent: 1 2024-04-10 11:01:38.902 2160-2160/? I/customActivity: onTouch: 1 2024-04-10 11:01:38.902 2160-2160/? I/CustomButton: onTouchEvent: 1

2024-04-10 11:01:38.908 2160-2160/? I/customActivity: onClick:

可以看到大概的执行顺序如下:

  1. ViewGroup: dispatchTouchEvent
  2. ViewGroup: onInterceptTouchEvent
  3. Button: dispatchTouchEvent
  4. Button: onTouch
  5. Button: onTouchEvent
  6. Button: onClick

3: 源码分析

我们简单看下源码:

  1. View的onTouchEvent:

    java 复制代码
    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;
    
        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.
            return clickable;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
    
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            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;
                    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 && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();
    
                            // 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();
                                }
                            }
                        }
    
                        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) {
                        checkForLongClick(0, x, y);
                        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) {
                        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);
                        checkForLongClick(0, x, y);
                    }
                    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) {
                        drawableHotspotChanged(x, y);
                    }
    
                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }
    
            return true;
        }
    
        return false;
    }

    我们关注下MotionEvent.ACTION_UP事件,可以看到在事件处理的最后有这么一段代码:

    java 复制代码
    if (mPerformClick == null) {
        mPerformClick = new PerformClick();
    }
    if (!post(mPerformClick)) {
        performClickInternal();
    }
  2. performClickInternal() 源码如下:

    java 复制代码
    private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();
    
        return performClick();
    }
  3. performClick()源码:

    java 复制代码
    public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();
    
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
    
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
        notifyEnterOrExitForAutoFillIfNeeded(true);
    
        return result;
    }

    可以看到在li != null && li.mOnClickListener != null条件下,执行了:

    li.mOnClickListener.onClick(this);

所以也可以解释了:

事件onTouchEvent()->onClick()的执行顺序.

然后我们看下View的dispathTouchEvent()的源码:

java 复制代码
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)) {        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)) {            result = true;        }        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.    if (actionMasked == MotionEvent.ACTION_UP ||            actionMasked == MotionEvent.ACTION_CANCEL ||            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {        stopNestedScroll();    }    return result;}

可以看到源码中的这段代码:

java 复制代码
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;}

这里调用了mOnTouchListener.onTouch事件.

所以也就证明了之前事件执行的顺序:

view的dispatchTouchEvent()->mOnTouchListener.onTouch()->onTouchEent()->onClick().

并且如果li.mOnTouchListener.onTouch(this, event)返回true,也可以看到onTouchEvent(event)并不会执行.

也就解释了我们前面如果修改了onTouch返回值为true,并不会执行onClick事件的原因.

接着我们在看下ViewGroup的dispatchTouchEvent()源码:

java 复制代码
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) {            // 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);            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);                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.        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 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;            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--) {                        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;                        }                        if (!canViewReceivePointerEvents(child)                                || !isTransformedTouchPointInView(x, y, child, null)) {                            ev.setTargetAccessibilityFocus(false);                            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);                        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();                            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;                }            }        }        // 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,                            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. actionMasked == MotionEvent.ACTION_DOWN 条件触发时,将mFirstTouchTarget置为null.

  2. disallowIntercept定义了是否禁用事件拦截.可以通过requestDisallowInterceptTouchEvent修改.默认情况下为false.

  3. 接着进入if条件,默认情况下,执行onInterceptTouchEvent.

    java 复制代码
    public boolean onInterceptTouchEvent(MotionEvent ev) {    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)            && ev.getAction() == MotionEvent.ACTION_DOWN            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)            && isOnScrollbarThumb(ev.getX(), ev.getY())) {        return true;    }    return false;}
  4. 在顺序执行可以看到dispatchTransformedTouchEvent()

    java 复制代码
    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;    }    // 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) {        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);            }            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;}

    可以看到主要会执行child.dispatchTouchEvent()方法.

这里其实也就验证了我们先前的调用顺序.

ViewGroup的dispatchTouchEvent->ViewGroup的onInterceptTouchEvent->子view的dispatchTouchEvent.

本文由博客一文多发平台 OpenWrite 发布!

相关推荐
wmd131643067123 分钟前
IDEA插件CamelCase,快速转变命名格式
java·ide·intellij-idea
捂月37 分钟前
Spring Boot 核心逻辑与工作原理详解
java·spring boot·后端
埋头编程~40 分钟前
【C++】踏上C++学习之旅(十):深入“类和对象“世界,掌握编程黄金法则(五)(最终篇,内含初始化列表、静态成员、友元以及内部类等等)
java·c++·学习
菜鸟起航ing1 小时前
Java中日志采集框架-JUL、Slf4j、Log4j、Logstash
java·开发语言·log4j·logback
Nightselfhurt1 小时前
RPC学习
java·spring boot·后端·spring·rpc
苹果醋31 小时前
vue3 在哪些方便做了性能提升?
java·运维·spring boot·mysql·nginx
孔汤姆1 小时前
部署实战(二)--修改jar中的文件并重新打包成jar文件
java·pycharm·jar
Abladol-aj2 小时前
并发和并行的基础知识
java·linux·windows
清水白石0082 小时前
从一个“支付状态不一致“的bug,看大型分布式系统的“隐藏杀机“
java·数据库·bug
吾日三省吾码8 小时前
JVM 性能调优
java