红橙Darren视频笔记 View事件分发源码分析 基于API29

首先Android的事件分发是基于责任链设计模式的 如果不理解责任链设计 可以参考:
https://blog.csdn.net/u011109881/article/details/59631314
提示:本文结合自己的代码实践更有助于理解

一 几个典型案例

public class MyView extends View {
    private static final String TAG = "MyView";

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "onClick: ");
            }
        });

        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d(TAG, "onTouch: " + event.getAction());
                return false;
            }
        });
    }

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

此时打印log如下

12-19 15:09:35.292  8259  8259 D MyView  : onTouch: 0
12-19 15:09:35.292  8259  8259 D MyView  : onTouchEvent: 0
12-19 15:09:35.341  8259  8259 D MyView  : onTouch: 2
12-19 15:09:35.341  8259  8259 D MyView  : onTouchEvent: 2
12-19 15:09:35.359  8259  8259 D MyView  : onTouch: 2
12-19 15:09:35.359  8259  8259 D MyView  : onTouchEvent: 2
12-19 15:09:35.374  8259  8259 D MyView  : onTouch: 2
12-19 15:09:35.374  8259  8259 D MyView  : onTouchEvent: 2
12-19 15:09:35.391  8259  8259 D MyView  : onTouch: 2
12-19 15:09:35.391  8259  8259 D MyView  : onTouchEvent: 2
12-19 15:09:35.413  8259  8259 D MyView  : onTouch: 2
12-19 15:09:35.413  8259  8259 D MyView  : onTouchEvent: 2
12-19 15:09:35.459  8259  8259 D MyView  : onTouch: 2
12-19 15:09:35.459  8259  8259 D MyView  : onTouchEvent: 2
12-19 15:09:35.459  8259  8259 D MyView  : onTouch: 1
12-19 15:09:35.459  8259  8259 D MyView  : onTouchEvent: 1
12-19 15:09:35.459  8259  8259 D MyView  : onClick:

按照责任链的思路 似乎onTouch的出在责任链发起端 onTouchEvent接着 最后是 onClick事件

二 分析原因 View的事件分发源码分析

二.一 OnTouchListener中的onTouch返回值可以决定onTouchEvent是否可以走到

    //事件分发的起点
	public boolean dispatchTouchEvent(MotionEvent event) {
        ...
		//是否消费 false意味着不消费 事件继续向责任链后端传递
        // true意味着消费事件(吃掉事件) 事件无法向责任链后端传递
        boolean result = false;

        ...
        if (actionMasked == MotionEvent.ACTION_DOWN) {//手势为下落的时候 停掉嵌套view的滚动
            // 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)) {
                //要执行onTouch 需要满足条件
                //1 li != null 2 li.mOnTouchListener != null 3 (mViewFlags & ENABLED_MASK) == ENABLED
                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;
    }

根据我们之前的分析 来到责任链的最前端 line22的onTouch事件
1 li != null
2 li.mOnTouchListener != null
3 (mViewFlags & ENABLED_MASK) == ENABLED
要执行这个代码 需要三个条件
为此我们要先看看li的类型ListenerInfo究竟是个啥

    static class ListenerInfo {
        /**
         * Listener used to dispatch focus change events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        @UnsupportedAppUsage
        protected OnFocusChangeListener mOnFocusChangeListener;

        /**
         * Listeners for layout change events.
         */
        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;

        protected OnScrollChangeListener mOnScrollChangeListener;

        /**
         * Listeners for attach events.
         */
        private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;

        /**
         * Listener used to dispatch click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        @UnsupportedAppUsage
        public OnClickListener mOnClickListener;

        /**
         * Listener used to dispatch long click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        @UnsupportedAppUsage
        protected OnLongClickListener mOnLongClickListener;

        /**
         * Listener used to dispatch context click events. This field should be made private, so it
         * is hidden from the SDK.
         * {@hide}
         */
        protected OnContextClickListener mOnContextClickListener;

        /**
         * Listener used to build the context menu.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        @UnsupportedAppUsage
        protected OnCreateContextMenuListener mOnCreateContextMenuListener;

        @UnsupportedAppUsage
        private OnKeyListener mOnKeyListener;

        @UnsupportedAppUsage
        private OnTouchListener mOnTouchListener;

        @UnsupportedAppUsage
        private OnHoverListener mOnHoverListener;

        @UnsupportedAppUsage
        private OnGenericMotionListener mOnGenericMotionListener;

        @UnsupportedAppUsage
        private OnDragListener mOnDragListener;

        private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;

        OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;

        OnCapturedPointerListener mOnCapturedPointerListener;

        private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;

        private WindowInsetsAnimationListener mWindowInsetsAnimationListener;

        /**
         * This lives here since it's only valid for interactive views.
         */
        private List<Rect> mSystemGestureExclusionRects;

        /**
         * Used to track {@link #mSystemGestureExclusionRects}
         */
        public RenderNode.PositionUpdateListener mPositionUpdateListener;
    }

我们看到ListenerInfo里面有我们熟悉的OnClickListener以及mOnTouchListener 那么我们在代码里调用setOnClickListener setOnTouchListener源码做了什么呢

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

    public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }

    @UnsupportedAppUsage
    ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

哦吼,发现了ListenerInfo是一系列Listener的集合,在我们调用setOnTouchListener的时候有两个条件已经满足
1 li != null 2 li.mOnTouchListener != null 那么剩下的条件3 (mViewFlags & ENABLED_MASK) == ENABLED如何判断呢
(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,默认都是enable的(这点我是听视频里面说的 有个实验可以验证这一点 就是在代码里面调用View的setEnabled(false)方法 发现即使注册了OnTouchListener 里面的onTouch也不会执行了
说到这里前面三个条件都成立了 那么li.mOnTouchListener.onTouch(this, event)开始执行,这就调用到我们代码里面的onTouch代码块了,而他的返回值决定了result(是否消费/吃掉事件),也就是事件是否继续执行,如果我们在onTouch返回了false(不吃掉事件)result维持默认值false,那么事件继续传递 line28 if (!result && onTouchEvent(event))得以继续执行,否则result=true onTouch无法继续执行(可以实验在OnTouchListener 里面的onTouch返回true看看

二.二onTouchEvent中的返回值可以决定onClick是否可以走到

同样是基于之前责任链设计思路 onClick必定在onTouchEvent之后调用,那么是在onTouchEvent内部呢还是在onTouchEvent调用之后呢?我们查看onTouchEvent的内部代码可以发现答案是前者

    public boolean onTouchEvent(MotionEvent event) {
        ...
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    ...
                            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();
                                }
                            }
                        }
					...
                    break;

                case MotionEvent.ACTION_DOWN:
                    ...
                    break;

                case MotionEvent.ACTION_CANCEL:
                    ...
                    break;

                case MotionEvent.ACTION_MOVE:
                    ...

                    break;
            }

            return true;
        }

        return false;
    }

    private final class PerformClick implements Runnable {
        @Override
        public void run() {
            recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
            performClickInternal();
        }
    }

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

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

可以看到在MotionEvent.ACTION_UP会调用PerformClick方法 其中会performClickInternal,最后调用的performClick中会调用到onClick事件
因此总结下来 View的事件分发的顺序是这样的
dispatchTouchEvent->OnTouchListener onTouch DOWN事件->onTouchEvent DOWN事件
->OnTouchListener onTouchMOVE事件->onTouchEvent MOVE事件
->OnTouchListener onTouchUP事件->onTouchEventUP事件
->OnClickListener onClick
其中任意一个环节 事件被吃掉(返回true)则后续的链子都断掉(收不到后续事件),不过这仅限于后面的层级,(不是直接把链条中间断开,后面的事件都接收不到的意思)
换一种说法,我们可以可以将OnTouchListener onTouchEvent OnClickListener 想象成3层不透光的纸,上面有个开关,return false可以打开开关 让光投到下一层,在上面的一系列事件中,比如我们 OnTouchListener onTouch MOVE的时候关闭了开关,那么后面两层的事件都无法接受到后续事件,但是注意,OnTouchListener onTouchUP事件还是可以接收到的,开关只影响后面的层级。说到这个开关 其实只有OnTouchListener onTouchEvent 有而已,活用这个开关,可以让我们在事件中只接受特定的事件。
https://blog.csdn.net/fengluoye2012/article/details/83782042
这里有个参考链接 里面的图片很适合理解事件分发

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值