事件分发机制

作者: CoderYuZ | 来源:发表于2019-04-26 17:33 被阅读0次

    事件顺序:

    事件序列.png

    Android中触摸事件主要相关的方法有三个:

    • 分发:dispatchTouchEvent(MotionEvent event)
    • 消费:onTouchEvent(MotionEvent event)
    • 拦截:onInterceptTouchEvent(MotionEvent event)
    一、事件从哪里开始,如果分发到View、ViewGroup?

    当手指触摸屏幕的时候,事件是先从Activity的dispatchTouchEvent开始分发的,欲知分发过程,直接打开Activity源码,看dispatchTouchEvent方法:

        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                //这个方法是个空的,供子类实现,不管他
                onUserInteraction();
            }
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }
    

    Activity的dispatchTouchEvent调用了getWindow().superDispatchTouchEvent(ev),也就是PhoneWindow的superDispatchTouchEvent()。继续深入,打开PhoneWindow源码:

        @Override
        public boolean superDispatchTouchEvent(MotionEvent event) {
            return mDecor.superDispatchTouchEvent(event);
        }
    

    PhoneWindow的superDispatchTouchEvent调用了mDecor的superDispatchTouchEvent,mDecor是DecorView的实例,DecorView集成自FrameLayout,所以事件就从Activity分发到了ViewGroup。顺序:Activity -> PhoneWindow -> DecorView -> ViewGroup

    Activity的dispatchTouchEvent方法总结:如果mDecor(ViewGroup)的superDispatchTouchEvent返回true,Activity的dispatchTouchEvent就返回true;不然就调用Activity的onTouchEvent()。也就是说如果Activity中的View没有消费掉事件,事件最后会来到Activity的onTouchEvent中。

    对PhoneWindow、DecorView的解释:View是如何被加载到屏幕窗口的


    二、ViewGroup的dispatchTouchEvent(MotionEvent event)

    代码有点多,说不清......来段伪代码:

        public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean concume = false;
            //调用onInterceptTouchEvent判断是否拦截
            if (onInterceptTouchEvent(ev)){
                //拦截则调用自身的onTouchEvent
                concume = onTouchEvent(ev);
            }else{
                //不拦截,将事件分发给子View
                concume = child.dispatchTouchEvent(ev);
            }
            return  concume
        }
    

    总之就是上面的注释,再抄一遍:调用onInterceptTouchEvent判断是否拦截,拦截则调用自身的onTouchEvent,不拦截,将事件分发给子View。
    ViewGroup的dispatchTouchEvent源码中有几个点,这里列出来,方便之后阅读源码的理解,完整流程里有很多判断条件,可以去源码里详细了解:

    • ACTION_DOWN事件会把mFirstTouchTarget清空,并且循环子View去拿到符合接受事件条件的View(判断是否动画中、是否超出边界等等),然后赋值给mFirstTouchTarget
    • 如果mFirstTouchTarget不为空,之后的ACTION_MOVE、ACTION_UP都传递给mFirstTouchTarget
    • 子View并不能通过parent.requestDisallowInterceptTouchEvent去阻止父View拦截ACTION_DOWN事件,其他事件可以。
    三、View的dispatchTouchEvent(MotionEvent event)
        public boolean dispatchTouchEvent(MotionEvent ev) {
              ...
                //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;
                }
              ...
        }
    

    在View的dispatchTouchEvent中可以看出,如果View设置了OnTouchListener,并且OnTouchListener中的onTouch方法返回true,那么就不会执行View的onTouchEvent方法了。所以OnTouchListener.onTouch的优先级大于View的onTouchEvent。如果onTouch消费掉了,则onTouchEvent不会执行。

    三、View的onTouchEvent(MotionEvent event)
        public boolean onTouchEvent(MotionEvent event) {
            ...
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            //首先上面有一段注释意思是不管View是否可用,他都可以消费事件,只是不给出响应。
            ...
        }
    

    源码去AS看,这里列出逻辑,方便之后阅读源码

    • 在ACTION_DOWN里进行长按事件监测,如果发现View是在可滑动容器中,则延迟100毫秒监测,否则直接检测
    • 首先判断长按事件,长按的判断是先在ACTION_DOWN发起一个500毫秒的延迟任务,如果发现ACTION_UP的时候不够500毫秒,会对这个延迟任务进行移除,则不会调用Listener的OnLongChick,然后判断点击。从使用角度也能理解,长按不会等到手指抬起就会触发,而点击需要手指抬起后才触发。
    • 如果设置了OnLongClickListener并且OnLongClickListener的onLongClick返回true,那么onClick则不会执行。那么问题OnLongClickListener中的onLongClick和OnClickListener中的onClick是否只能执行一个?答案显然不是。只要onLongClick返回false,onClick也会执行。
    • View的事件顺序:dispatchTouchEvent -> OnTouchListener.onTouch -> onTouchEvent -> onLongClick -> onClick。 只有中间有返回ture,后面就接受不到事件。

    总结

    事件分发流程图(责任链模式):


    事件分发模型.png

    事件分发结论:

    • 一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有不定数的MOVE事件,以UP事件结束。
    • 正常情况下,一个事件序列只能被一个View拦截并且消耗
    • 某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onInterceptTouchEvent不再会调用。
    • 某个View一旦开始处理事件,如果他不消耗ACTION_ DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用)。
    • 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外。
    • ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false。View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用
    • View的onTouchEvent默认会消费事件(返回true),除非它是不可点击的(chickable和longClickable同时为false)。View的longClickable默认都为false,chickable要分情况,比如Button的chickable默认为true,TextViewn的chickable默认为false。
    • View的enable属性不影响onTouchEvent的默认返回值,哪怕一个View是disable状态的,只要他的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true。
    • onClick会响应的前提是当前VIew是可点击的,并且收到了ACTION_DOWN和ACTION_UP事件,并且受长按事件影响,当长按事件返回true时,onClick不会响应。
    • onLongClick在ACTION_DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener。

    相关文章

      网友评论

        本文标题:事件分发机制

        本文链接:https://www.haomeiwen.com/subject/zanfnqtx.html