美文网首页
Android事件分发 -- Activity分发事件

Android事件分发 -- Activity分发事件

作者: 蜗牛是不是牛 | 来源:发表于2022-05-11 17:36 被阅读0次

    前言

    基础知识巩固系列。

    正文

    对于点击事件(这里当然是广义的,包括滑动事件等)的传递,简单来说就是系统产生的Event传递到Activity,再传递到ViewGroup,最后传递到具体View的流程,在这个过程中可以对Event进行拦截,来控制其Event传递。

    常用的3个方法

    在事件分发时,这里有3个方法肯定是老朋友了,我们来简单介绍一下。

    dispatchTouchEvent

    字面意思就是分发TouchEvent,当Event传递到当前组件(Activity/ViewGroup/View)时,该方法必调用;换个角度来理解,想把一个Event传递给某个组件,就必须要调用该组件的dispatchTouchEvent方法。

    既然用来分发Event,做的事也就是这个Event要分发给谁,如果是ViewGroup,它需要做出抉择是分发给某个子View,还是自己处理,还是其他情况;如果是View,需要把Event分发给哪个处理器来处理,还是其他情况。

    所以dispatchTouchEvent的返回值就表示是否消耗了这个Event,至于是它自己消耗的,还是子View消耗的,这个具体看逻辑,这个返回值只对上一层调用者有作用。

    onInterceptTouchEvent

    字面意思就是拦截Event,当Event传递到ViewGoup时,使用该方法决定是否拦截,从上面也可以推断出,这个方法就是在dispatchTouchEvent方法内部调用的。

    默认的逻辑就是如果拦截了,就这个ViewGroup自己处理,如果没有拦截,则继续向下分发。

    这里要清楚一个Event一般是一个事件流,比如滑动事件就是 DOWN -> N个MOVE -> UP这种,所以对Event要如何拦截是有讲究的,我们下篇文章里细说。

    还有就是系统默认的该方法实现中的细节要理解清楚,里面涉及了内部拦截法实现的关键,还是下篇文章里细说。

    onTouchEvent

    当当前对象需要对Event进行处理时调用,返回值的含义便是是否消耗掉事件。

    这里的重点关注就是当View决定去处理该Event时,会有不同的处理器,这是有先后顺序的,细节我们放在下下篇文章里细说。

    Activity分发事件

    这里直接从事件传递到Activity说起,至于系统如何产生事件,我们暂时不做讨论。

    首先我们在Activity代码中,重写方法,只能重写下面2个方法:

    说明Activity是无法通过重写onInterceptTouchEvent来拦截事件的。

    Activity的dispatchTouchEvent方法

    我们先要知道一个点,就是系统把点击事件传递到Activity就是通过调用Activity的dispatchTouchEvent方法,与其说是传递,也就是调用该方法

    //这是Activity源码的方法
    //这里的参数就是系统传递给Activity的点击事件
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //这个可以不用分析
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //当该条件为true时,直接返回方法
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //上面条件为false时,调用onTouchEvent方法
        return onTouchEvent(ev);
    }
    
    

    我们来看一下 getWindow().superDispatchTouchEvent()方法:

    //Window类,是抽象类
    public abstract boolean superDispatchTouchEvent(MotionEvent event);
    
    

    我们找到默认唯一实现类PhoneWindow类,也就是调用下面方法:

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

    会发现这里有个特别熟悉的地方,就是mDecor,即DecorView,看一下DecorView类中该方法:

    //DecorView类
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    
    

    会发现这里调用了父类的dispatchTouchEvent,我们看看是啥:

    //DecorView就是FrameLayout子类
    public class DecorView extends FrameLayout
    
    
    //所以这里调用的就是ViewGroup的dispatchTouchEvent方法了
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        ....
        }
    
    

    是不是到这里豁然开朗,居然调用到了ViewGroup的分发事件方法,其实这就完成了把点击事件传递到了ViewGroup的过程

    事件由Activity -> 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代码决定,它涉及了它下面所有View的判断,也就是当 getWindow().superDispatchTouchEvent()方法执行完时,事件都已经传递下去了,假如你点击的一个按钮外面包裹了10层布局,那至少已经分发了10层以上才可以拿到结果,所以这个方法会耗时较久。

    责任链思想

    上面的dispatchTouchEvent代码中,逻辑是当window能处理时,则返回,如果不能处理则调用onTouchEvent方法。

    其实这种思想就是责任链思想,把一个点击事件层层传递,从Activity开始,当有一层进行处理了,这个方法就可以返回了,这个调用深度很深。

    这个思想很重要,它有个很关键的地方就是兜底思想,比如这里中间有一层它把事件分发到下一层之后都没有处理消费,那这一层自己就可以自己再处理,就比如Activity的方法中,当getWindow().superDispatchTouchEvent返回false时,则再调用onTouchEvent方法。

    Activity的onTouchEvent方法

    这里我们要了解一点,就是这个Activity的onTouchEvent方法调用的条件,那就是它下面的View没有消费掉事件,至于什么情况下是没有消费掉事件,后面再细说。

    所以这就是兜底思想,当Activity的小弟都无法消费掉这个事件,就有Activity这个老大来处理,所以你在Activity中重载onTouchEvent方法,这个方法不一定会被调用。

    重写dispatchTouch实现从Activity拦截事件

    既然知道Activity的dispatchTouchEvent的分发流程,我们就可以干些操作,比如让整个页面都无法点击。

    //需要屏蔽点击事件的Activity
     override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    //        return super.dispatchTouchEvent(ev)
            return true
        }
    
    

    我这里直接不调用super.dispatchTouchEvent方法,就不会把点击事件给分发出去,所以我们平时写代码必须要注意这个super.xxx方法的调用,并不是可有可无的,要理解其原理。

    方法返回值

    其实Activity作为事件分发的老大,它的2个方法返回值其实影响并不大了。

    • 对于dispatchTouchEvent的返回值,不论是返回true还是false都表示事件分发结束了,它的返回值是给它上一级看的,true就表示把事件消耗掉。

    • 对于onTouchEvent的返回值也一样,表示是否消耗掉这个事件,不过这个返回值是给它同级的Activity的dispatchTouchEvent方法来调用的,含义不一样。

    总结

    关于流程,我直接从网上找了一个很经典的图

    这篇文章内容很少,我们做个总结:

    • Activtiy中只能重写dispatchTouchEvent和onTouchEvent方法,无法直接拦截事件。

    • Activity把事件传递给ViewGroup是通过先传递给Window,再传递给decorView实现的。

    • dispatchTouchEvent方法中当所有的View都没有消费掉事件时,才调用onTouchEvent方法。

    • 责任链和兜底思想,也就是从大到小依次调用,当手下没有解决时,自己再处理。

    • 当重写时,要注意调用super的时机,比如这里可以重写dispatchTouchEvent方法,让事件不进行传递。

    相关文章

      网友评论

          本文标题:Android事件分发 -- Activity分发事件

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