Android事件分发 | Activity分发事件

1,846 阅读6分钟

前言

基础知识巩固系列。

该系列文章链接:

Android事件分发 | ViewGroup分发事件 - 掘金 (juejin.cn)

Android事件分发 | View分发事件 - 掘金 (juejin.cn)

正文

对于点击事件(这里当然是广义的,包括滑动事件等)的传递,简单来说就是系统产生的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个方法:

image.png

说明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方法来调用的,含义不一样。

总结

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

image.png

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

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

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

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

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

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