美文网首页Android自定义ViewAndroid开发Android开发
10期 | 写给小白-Android事件分发机制

10期 | 写给小白-Android事件分发机制

作者: 程序员星星_ | 来源:发表于2019-04-17 16:17 被阅读12次
    Android Developer.jpeg

    Android事件分发机制是高级工程师必须烂熟于心的知识点,本小姐手撕源码后总结了以下文章,方便大家一起学习。

    事件分发机制的传递过程:

    当我们点击屏幕时,首先会按下屏幕,这个动作触发的事件其实就是MotionEvent.ACTION_DOWN,那么这个事件是如何进行传递的呢?

    点击屏幕时,屏幕正处于Activity界面,Activity里有一个dispatchTouchEvent方法会进行事件的分发,请看源码:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    

    当操作事件为ACTION_DOWN时,会和用户执行交互。Activity会将事件传递给Window,getWindow().superDispatchTouchEvent()是由Window来进行事件的分发,如果Window消费了该事件,getWindow().superDispatchTouchEvent()为true值,那么直接return退出该方法,如果window没有消费该事件,那么继续由Activity的onTouchEvent方法来处理。

    小贴士:

    return的用法:结束该方法,继续执行方法后面的语句。

    接下来继续看Window的源码:

    public abstract boolean superDispatchTouchEvent(MotionEvent event);
    

    Window类是一个抽象类,里边的superDispatchTouchEvent方法是一个抽象方法,看Window类的介绍,这个抽象类的唯一存在实现是PhoneWindow,那么我们去看PhoneWindow的源码。

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

    从上面可以看出,PhoneWindow将事件继续传递给了DecorView,DecorView是Window窗口的顶级视图,包含窗口的装饰。DecorView是PhoneWindow的一个内部类。

    接下来我们继续看DecorView的superDispatchTouchEvent方法。
    DecorView实际上是一个ViewGroup,我们来看ViewGroup是如何进行事件分发的,请看源码:
    下面的源码是我把不太主要的逻辑删减后的代码,ViewGroup的dispatchTouchEvent:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //注释1
        boolean handled = false;
        
        if (onFilterTouchEventForSecurity(ev)) {
            // 注释2
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
           
            //注释3
            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;
            }
            //注释4
            if (!canceled && !intercepted) {
                handled = child.dispatchTouchEvent(ev);
            }
        }
        return handled;
    }
    

    注释1处:定义的handled值为事件是否被消费,默认为false,即事件默认没有被ViewGroup消费,会向子View传递。

    注释2处:初始化Down事件,初始化并重置手势事件。初始化的原因:一个完整的事件序列是以DOWN开始,以UP结束的。如果是DOWN事件,说明这是一个新的事件序列,所以需要初始化之前的状态。

    注释3处:检查拦截。如果按下的事件是DOWN事件,然后判断是否设置了标志位FLAG_DISALLOW_INTERCEPT,标志位和requestDisallowInterceptTouchEvent共同作用,表示要求不允许父View拦截事件。默认不设置标志位,则执行onInterceptTouchEvent方法,如果设置了标志位,则父View不拦截事件,会转到注释4,向子View传递继续执行事件。回到注释3,如果按下的不是DOWN事件,有ACTION_MOVE和ACTION_UP事件到来时,同一事件中的其他序列都会默认交给ViewGroup来处理。

    划重点:

    标志位FLAG_DISALLOW_INTERCEPT的作用:
    禁止ViewGroup拦截除Down之外的事件。意思就是除down之外的事件只要设置了标志位都会向子View进行传递。
    因为每次有ACTION_DOWN事件传过来时,都会在注释2处重置手势的状态。

    接下来我们继续看ViewGroup的onInterceptTouchEvent方法:

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

    默认返回false,向子View传递。根据源码对该方法的注释,onInterceptTouchEvent返回true时会执行onTouchEvent方法。但是如果设置了onTouchListener,会先执行onTouch方法,onTouch方法没有被消费才会执行onTouchEvent方法,如果onTouch事件被消费就不会再继续执行onTouchEvent方法。

    划重点:

    1.ViewGroup想要拦截事件,则可以重写onInterceptTouchEvent方法返回true。
    因为:该方法默认为false,向子View传递。
    2.onInterceptTouchEvent方法不是每次事件都会调用。
    因为:触发的事件为ACTION_DOWN,如果设置了FLAG_DISALLOW_INTERCEPT标志位,就会向子View传递,就不会执行该方法。

    小贴士:

    dispatchTouchEvent:负责事件分发。
    onInterceptTouchEvent:负责事件拦截。

    最后我们再来看看View的dispatchTouchEvent源码:
    下面是我简化逻辑后的代码:

    public boolean dispatchTouchEvent(MotionEvent event) {
        //注释1处
        boolean result = false;
        //注释2处
        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;
            }
        }
        return result;
    }
    

    从源码可以看出:事件如果被View的dispatchTouchEvent消费则返回true,如果没有则返回false。

    注释1处:定义事件最后有没有被View的dispatchTouchEvent消费,默认为false。

    注释2处:判断是不是有onTouchListener,如果有执行onTouch方法,如果事件被onTouch方法消费不会继续往下执行,如果没有被消费,继续执行onTouchEvent方法。

    接下来我们继续看View的onTouchEvent方法:
    下面是我简化逻辑后的源码:

    public boolean onTouchEvent(MotionEvent event) {
        //注释1处
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        //注释2处
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    performClick();
                    break;
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_CANCEL:
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
            }
            return true;
        }
        return false;
    }
    

    从上面源码可以看出,只要View的Click有点击事件,就会执行action事件,事件就会被处理返回true,否则事件没有被消费返回false。
    当ACTION_UP事件触发时,会执行performClick方法,看performClick方法的源码:

    public boolean performClick() {
        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;
        }
        return result;
    }
    

    如果设置了onClickListener方法,那么就会执行onClick方法,事件就会被消费。

    小贴士:

    View没有onInterceptTouchEvent方法,当有点击事件传递给它时,就会执行onTouchEvent方法。

    View的onTouchEvent方法默认都会消耗事件,返回true。但是如果View不可点击就不会消耗事件。

    onClick事件会发生的前提:当前View可点击,并且收到了down和up事件。

    Activity到DecorView布局的加载过程:

    初始化Activity时调用setContentView加载布局,然后执行getWindow().setContentView传给Window,PhoneWindow里创建了DecorView,而DecorView又将屏幕划分为两个区域:一个是 TitleView,另一个是ContentView,我们平常做应用所写的布局正展示在ContentView中。

    快问快答小测试:

    1.Activity中为什么会有dispatchTouchEvent?
    答:Android中每个层级对触摸事件的处理都是从dispatchTouchEvent开始的。
    2.事件分发机制的传递规则。
    事件分发机制从Activity传给Window传给ViewGroup传给View。

    相关文章

      网友评论

        本文标题:10期 | 写给小白-Android事件分发机制

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