Android | 彻底理解 View 的事件分发机制

前言

网络上有很多关于 View 事件分发机制的文章,我本人也阅读过不少,大部分一上来就是各种结论和源码分析,暂不评价这些分析和结论是否正确,单从这样的出发点去学习 View 的事件分发机制就不恰当。

Android View 的事件分发过程情况多样,本文会带领读者在不看一行源代码的情况下,通过一个实例和十几张事件分发流程图来完整分析 View 的事件分发机制。话不多说,实践是检验真理的唯一标准,让我们开始学习吧。

本文大纲

View 事件分发思维导图.png

准备工作

View 的事件分发是由外向内的,总是从父 View 传递给子 View。在这里我写了三个自定义 View 来直观的分析这个过程,这种方式也最好理解。

这三个 View 是 OuterViewGroupInnerViewGroupMyView。分别继承自 ViewGroupViewGroupView。对于 OuterViewGroupInnerViewGroup 来说,需要重写 onMeasure 方法测量所有子 View,同时重写 onLayout 方法来确定子 View 相对自身的摆放位置,Kotlin 代码如下。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    measureChildren(widthMeasureSpec, heightMeasureSpec)
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    for (i in 0 until childCount){
        val child = getChildAt(i)
        child.layout(0, 0, child.measuredWidth, child.measuredHeight)
    }
}
复制代码

上述代码在 onMeasure 中调用 measureChildren 测量了所有的子 View,在 onLayout 中将每一个子 View 放置在自身的左上角位置。然后我们在 TestActivity 的布局文件中将这三个 View 显示出来,xml 代码如下。

<?xml version="1.0" encoding="utf-8"?>
<cn.blogss.core.test.OuterViewGroup
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/green">
    <cn.blogss.core.test.InnerViewGroup
        android:layout_width="@dimen/dp_300"
        android:layout_height="@dimen/dp_400"
        android:background="@color/gray">
        <cn.blogss.core.test.MyView
            android:layout_width="@dimen/dp_200"
            android:layout_height="@dimen/dp_200"
            android:background="@color/burlywood"/>
    </cn.blogss.core.test.InnerViewGroup>
</cn.blogss.core.test.OuterViewGroup>
复制代码

最终的显示效果如下。

1645068025.png

有读者可能会问,直接用两个 LinearLayout 套一个 Button 不是一样的效果吗,何必多此一举。其实不然,系统自带的这些控件内部很多都对 View 的事件做了拦截(消费)处理,不便于我们得到一个正确的结果,所以自定义 View 继承 ViewGroup 或 View来进行分析,最后的结果一定是最准确、纯净、原始的。

1. 默认的事件分发过程

简单的准备好实验条件后,首先来看一下 View 默认的事件分发流程。默认是指我们不对 View 的任何事件做拦截处理,遵循系统的默认规则。在这种情况下,重写 OuterViewGroupInnerViewGroupMyViewTestActivitydispatchTouchEventonTouchEvent 方法,日志打印出他们的调用顺序,即可得到 View 事件分发的过程。伪代码如下,省略了日志打印的代码。

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    return super.dispatchTouchEvent(ev)
}

override fun onTouchEvent(event: MotionEvent): Boolean {
    return super.onTouchEvent(event)
}
复制代码

接着用手指在 MyView 上轻微滑动一下然后抬起,产生一次完整的 ACTION_DOWN,ACTIOIN_MOVE,ACTION_UP 事件,下文所述的事件分发流程均基于这个动作。需要注意的是,View 或 ViewGroup 的 dispatchTouchEventonTouchEvent 方法默认返回值都是 false,即不拦截任何事件。下面是手指在 MyView 上滑动抬起过程中,事件的传递结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN
MyView: dispatchTouchEvent: action = ACTION_DOWN
MyView: onTouchEvent: action = ACTION_DOWN
InnerViewGroup: onTouchEvent: action = ACTION_DOWN
OuterViewGroup: onTouchEvent: action = ACTION_DOWN
TestActivity: onTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
TestActivity: onTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
TestActivity: onTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

为了让具体抽象化,读者可以将本文的 TestActivity 对应流程图中的 ActivityOuterViewGroup 对应 ViewGroup1InnerViewGroup 对应 ViewGroup2MyView 对应 View

根据日志的结果,可以画出默认的事件分发流程图如下。

默认的事件分发.png

ACTION_DOWN 事件的传递

根据上面日志的结果,ACTION_DOWN 由 TestActivity 的 dispatchTouchEvent 一直传递到 MyView 的 dispatchTouchEvent,然后由 MyView 的 onTouchEvent 向外传递到 TestActivity 的 onTouchEvent。

ACTION_MOVE 事件的传递

ACTION_MOVE 是在 ACTION_DOWN 事件后产生的,因为没有任何的 Activity 或 View 对 ACTION_DOWN 事件拦截,所以 ACTION_MOVE 传递到了 TestActivity 后就停止了。可以发现,ACTION_MOVE 事件的传递是受 ACTION_DOWN 事件影响的。

ACTION_UP 事件的传递

和 ACTION_MOVE 事件一样,受 ACTION_DOWN 事件拦截影响,ACTION_UP 传递到 TestActivity 后也停止向下传递了。

总结

根据上面的分析,在默认情况下,有如下结论。

  1. 对于 ACTION_DOWN 事件,会从最外层的 dispatchTouchEvent 传递到最内层的 dispatchTouchEvent,然后由最内层的 onTouchEvent 传递到最外层的 onTouchEvent

  2. 对于 ACTION_MOVE 和 ACTION_UP 来说,只会传递到 Activity 的 dispatchTouchEventonTouchEvent 中。

2. 在 dispatchTouchEvent 中拦截事件

2.1 在 Activity 的 dispatchTouchEvent 中拦截事件

修改 TestActivity 的代码,在 dispatchTouchEvent 中返回 true 来拦截一个完整的事件。

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    return true
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
TestActivity: dispatchTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 Activity 的 dispatchTouchEvent 中拦截事件的流程图如下。

在 Activity 的 dispatchTouchEvent 返回 true.png

ACTION_DOWN 事件的传递

ACTION_DOWN 事件传递到 TestActivity 就停止向下传递了。

ACTION_MOVE 事件的传递

与 ACTION_DOWN 事件一样传递到 TestActivity 就停止向下传递了。

ACTION_UP 事件的传递

与 ACTION_DOWN 一样传递到 TestActivity 就停止向下传递了。

2.2 在 ViewGroup 的 dispatchTouchEvent 中拦截事件

修改 InnerViewGroup 的代码,在 dispatchTouchEvent 中返回 true 来拦截一个完整的事件。

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    return true
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
OuterViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: dispatchTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
OuterViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: dispatchTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 ViewGroup2 的 dispatchTouchEvent 中拦截事件的流程图如下。

在 ViewGroup2 的 dispatchTouchEvent 返回 true.png

ACTION_DOWN 事件的传递

因为在 InnerViewGroup 的 dispatchTouchEvent 拦截了所有事件,所以 ACTION_DOWN 事件只能从 TestActivity 传递到 InnerViewGroup,而 MyView 接收不到任何事件。

ACTION_MOVE 事件的传递

ACTION_MOVE 从 TestActivity 传递到 InnerViewGroupMyView 接收不到任何事件。

ACTION_UP 事件的传递

ACTION_UP 从 TestActivity 传递到 InnerViewGroupMyView 接收不到任何事件。

这里读者思考一个问题,如果在 ViewGroup1 的 dispatchTouchEvent 中拦截事件,流程会是怎么样的呢?

2.3 在 View 的 dispatchTouchEvent 中拦截事件

修改 MyView 的代码,在 dispatchTouchEvent 中返回 true 来拦截一个完整的事件。

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    return true
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN
MyView: dispatchTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
OuterViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: dispatchTouchEvent: action = ACTION_MOVE
MyView: dispatchTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
OuterViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: dispatchTouchEvent: action = ACTION_UP
MyView: dispatchTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 View 的 dispatchTouchEvent 中拦截事件的流程图如下。

在 View 的 dispatchTouchEvent 返回 true.png

ACTION_DOWN 事件的传递

ACTION_DOWN 从最外层(TestActivity)传递到了最内层(MyView)。

ACTION_MOVE 事件的传递

ACTION_MOVE 从最外层(TestActivity)传递到了最内层(MyView)。

ACTION_UP 事件的传递

ACTION_UP 从最外层(TestActivity)传递到了最内层(MyView)。

总结

根据上面的分析,在 dispatchTouchEvent 中拦截事件,可以得出如下结论。

  1. ActivityViewGroupView 的 dispatchTouchEvent 中拦截事件,所有的事件只能传递到拦截的地方,而处于其内部的控件将无法收到任何事件。

3. 在 onTouchEvent 中拦截事件

上面我们分析了在 dispatchTouchEvent 中拦截事件的事件传递规则,发现所有的事件会从最外层的 dispatchTouchEvent 传递到拦截处的 dispatchTouchEvent 就停止了。那么现在来看看在 onTouchEvent 中拦截事件的事件传递规则是怎么样的。

3.1 在 Activity 的 onTouchEvent 中拦截事件

修改 TestActivity 的代码,在 onTouchEvent 中返回 true 来拦截一个完整的事件。

override fun onTouchEvent(event: MotionEvent): Boolean {
    return true
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN
MyView: dispatchTouchEvent: action = ACTION_DOWN
MyView: onTouchEvent: action = ACTION_DOWN
InnerViewGroup: onTouchEvent: action = ACTION_DOWN
OuterViewGroup: onTouchEvent: action = ACTION_DOWN
TestActivity: onTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
TestActivity: onTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
TestActivity: onTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 Activity 的 onTouchEvent 中拦截事件的流程图如下,和默认的事件分发过程是一样的。

在 Activity 的 onTouchEvent 返回 true.png

ACTION_DOWN 事件的传递

ACTION_DOWN 事件和默认的传递规则一样。由 TestActivity 的 dispatchTouchEvent 一直传递到 MyView 的 dispatchTouchEvent,然后由 MyView 的 onTouchEvent 向外传递到 TestActivity 的 onTouchEvent。

ACTION_MOVE 事件的传递

ACTION_MOVE 事件和默认的传递规则一样,传递到 TestActivity 后就停止了。

ACTION_UP 事件的传递

ACTION_UP 事件和默认的传递规则一样,传递到 TestActivity 后就停止了。

3.2 在 ViewGroup 的 onTouchEvent 中拦截事件

修改 InnerViewGroup 的代码,在 onTouchEvent 中返回 true 来拦截一个完整的事件。

override fun onTouchEvent(event: MotionEvent): Boolean {
    return true
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN
MyView: dispatchTouchEvent: action = ACTION_DOWN
MyView: onTouchEvent: action = ACTION_DOWN
InnerViewGroup: onTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
OuterViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: onTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
OuterViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: onTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 ViewGroup2 的 onTouchEvent 中拦截事件的流程图如下。

在 ViewGroup2 的 onTouchEvent 返回 true.png

ACTION_DOWN 事件的传递

ACTION_DOWN 事件由 TestActivity 的 dispatchTouchEvent 一直传递到 MyView 的 dispatchTouchEvent,然后由 MyView 的 onTouchEvent 向外传递到 InnerViewGroup 的 onTouchEvent。

ACTION_MOVE 事件的传递

ACTION_MOVE 事件由 TestActivity 的 dispatchTouchEvent 一直传递到 InnerViewGroup 的 dispatchTouchEvent,然后到达 InnerViewGroup 的 onTouchEvent 后停止。

ACTION_UP 事件的传递

和 ACTION_MOVE 事件一样。

3.3 在 View 的 onTouchEvent 中拦截事件

修改 MyView 的代码,在 onTouchEvent 中返回 true 来拦截一个完整的事件。

override fun onTouchEvent(event: MotionEvent): Boolean {
    return true
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN
MyView: dispatchTouchEvent: action = ACTION_DOWN
MyView: onTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
OuterViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: dispatchTouchEvent: action = ACTION_MOVE
MyView: dispatchTouchEvent: action = ACTION_MOVE
MyView: onTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
OuterViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: dispatchTouchEvent: action = ACTION_UP
MyView: dispatchTouchEvent: action = ACTION_UP
MyView: onTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 View 的 onTouchEvent 中拦截事件的流程图如下。

在 View 的 onTouchEvent 返回 true.png

ACTION_DOWN 事件的传递

ACTION_DOWN 事件由外向内传递到 MyView,因为在 MyView 的 onTouchEvent 中拦截了事件,所以 ACTION_DOWN 在 MyView 停止向外传递。

ACTION_MOVE 事件的传递

与 ACTION_DOWN 事件传递流程一样。

ACTION_UP 事件的传递

与 ACTION_DOWN 事件传递流程一样。

总结

我们已经分析完了在 dispatchTouchEvent 和 onTouchEvent 中拦截事件的事件传递规则,现在结合两种拦截方式做一个总结,看看这两种拦截方式的内在联系。

  1. 可以发现,事件传递遵循由外向内(外层的 dispatchTouchEvent 到内层的 dispatchTouchEvent),然后由内向外(内层的 onTouchEvent 到外层的 onTouchEvent)的顺序

  2. 如果事件在由外向内传递过程中被拦截(消费)了,那么由内向外的过程就不会发生,这也很容易理解。

  3. 在 onTouchEvent 中拦截(消费)事件,不影响事件由外向内传递到拦截处的过程。

  4. 在 onTouchEvent 中拦截(消费)事件,只影响事件由内向外传递的过程,对于 ACTION_MOVE,其内外层的 onTouchEvent 均不会被调用。

  5. 从打印出的日志和流程图可以发现,ACTION_MOVE 和 ACTION_UP 事件在使用任何拦截策略下,他们的传递流程都是一样的,包括下文将要讨论的拦截策略。

4. 在 onInterceptTouchEvent 中拦截事件

因为 onInterceptTouchEvent 方法在遇到滑动冲突的场景很有使用价值。所以我们有必要研究一下在 ViewGroup 的 onInterceptTouchEvent 方法中拦截事件,事件的传递顺序是怎样的。

4.1 在 onInterceptTouchEvent 中拦截所有事件

InnerViewGroup 中重写 onInterceptTouchEvent 返回 true。

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
    return true
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: onTouchEvent: action = ACTION_DOWN
OuterViewGroup: onTouchEvent: action = ACTION_DOWN
TestActivity: onTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
TestActivity: onTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
TestActivity: onTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 ViewGroup2 的onInterceptTouchEvent 中拦截所有事件的流程图如下。

在 ViewGroup2 的 onInterceptTouchEvent 返回 true.png

ACTION_DOWN 事件的传递

ACTION_DOWN 事件由外向内传递到 InnerViewGroup,然后由内向外传递到 TestActivity。

ACTION_MOVE 事件的传递

ACTION_MOVE 事件只传递到了 TestActivity,并没有传递到 InnerViewGroup。

ACTION_UP 事件的传递

和 ACTION_MOVE 事件一样。

4.2 在 onInterceptTouchEvent 和 onTouchEvent 中拦截事件

InnerViewGroup 中重写 onInterceptTouchEvent 和 onTouchEvent,返回 true,代码如下。

override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
    return true
}

override fun onTouchEvent(ev: MotionEvent): Boolean {
    return true
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: onTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
OuterViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: onTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
OuterViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: onTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 onInterceptTouchEvent 和 onTouchEvent 中拦截事件的流程图如下。

在 ViewGroup2 的 onInterceptTouchEvent 和 onTouchEvent返回 true.png

ACTION_DOWN 事件的传递

ACTION_DOWN 事件传递到 InnerViewGroup 后停止。

ACTION_MOVE 事件的传递

ACTION_MOVE 事件传递到 InnerViewGroup 后停止。

ACTION_UP 事件的传递

ACTION_UP 事件传递到 InnerViewGroup 后停止。

总结

  1. 单纯在 onInterceptTouchEvent 中拦截,ACTION_DOWN 会在拦截处停止传递,而后续的 ACTION_MOVE 只会传递到 Activity。

  2. 而如果同时在 onInterceptTouchEvent 和 onTouchEvent 中拦截事件,后续的 ACTION_MOVE 可以传递到拦截处。

5. 拦截 ACTION_MOVE 事件(必会)

以上分析基本覆盖了事件所有的拦截策略,此时读者应该对事件传递的流程有了全局的了解。接下来我们要研究的是拦截 ACTION_MOVE 事件,这部分是全文的重点,希望读者能够牢牢掌握。

研究 View 事件分发机制的目的是为了能够写出一个标准的自定义 View,在面对一些有手势交互的自定义 View 或者滑动冲突时,我们能解决这些问题。而处理 ACTION_MOVE 事件就是重点。

5.1 在 onInterceptTouchEvent 中拦截 ACTION_MOVE 事件

需要注意的是,如果想要拦截 ACTION_MOVE 事件,那么 ACTION_MOVE 就必须能够传递到拦截点。如果连 ACTION_MOVE 都不能到达拦截处,那么讨论拦截 ACTION_MOVE 就很荒谬。根据前面的分析,在 MyView 的 onTouchEvent 中返回 true,这样 ACTION_MOVE 就能够由最外层传递到最内层,再来回顾一下前面 3.3 小节的这张图。

在 View 的 onTouchEvent 返回 true.png

因此重写 MyView 的 onTouchEvent 返回 true。

override fun onTouchEvent(ev: MotionEvent): Boolean {
    return true
}
复制代码

然后在 InnerViewGroup 中重写 onInterceptTouchEvent 来拦截 ACTION_MOVE 事件,其他事件遵循默认的规则,即不拦截,代码如下。

override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
    when(ev.action) {
        MotionEvent.ACTION_MOVE -> {
            return true
        }
    }
    return super.onInterceptTouchEvent(ev) 
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN
MyView: dispatchTouchEvent: action = ACTION_DOWN
MyView: onTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
OuterViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: dispatchTouchEvent: action = ACTION_MOVE
MyView: dispatchTouchEvent: action = ACTION_CANCEL
MyView: onTouchEvent: action = ACTION_CANCEL

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
OuterViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: onTouchEvent: action = ACTION_MOVE
TestActivity: onTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
OuterViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: onTouchEvent: action = ACTION_UP
TestActivity: onTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 onInterceptTouchEvent 中拦截 ACTION_MOVE 事件的流程图如下。

在 onInterceptTouchEvent 中拦截 ACTION_MOVE 事件.png

ACTION_DOWN 事件的传递

因为我们没有对 ACTION_DOWN 事件做任何的拦截处理,只在 MyView 的 onTouchEvent 返回了 true,所以 ACTION_DOWN 事件的传递,和在 3.3 小节中 ACTION_DOWN 的流程是一致的。

ACTION_MOVE 事件的传递

为了让结果更加清晰,这里我触发了两次 ACTION_MOVE 事件。

第一次 ACTION_MOVE 由 TestActivity 传递到了 InnerViewGroup。因为我们在 InnerViewGroup 中拦截了 ACTION_MOVE,所以肯定传递不到 MyView。取而代之的是 ACTION_MOVE 变成了 ACTION_CANCEL 传递到了 MyView 中。需要注意,当我们收到ACTION_CANCEL消息时,就表示该控件后续不会再获得消息

可以看见,第二次及其之后的 ACTION_MOVE 永远传递不到 MyView

ACTION_UP 事件的传递

与第二次及其之后的 ACTION_MOVE 传递流程一致。

5.2 在 dispatchTouchEvent 中拦截 ACTION_MOVE 事件

除了在 onInterceptTouchEvent 中拦截 ACTION_MOVE 事件之外,还可以在 dispatchTouchEvent 中进行拦截。修改 InnerViewGroup 的代码如下,在 dispatchTouchEvent 中拦截 ACTION_MOVE,其他事件保持默认处理,即不拦截。

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    when(ev.action) {
        MotionEvent.ACTION_MOVE -> {
            return true
        }
    }
    return super.dispatchTouchEvent(ev)
}
复制代码

MyView 的 onTouchEvent 仍然返回 true。

override fun onTouchEvent(ev: MotionEvent): Boolean {
    return true
}
复制代码

下面是事件传递的结果。

TestActivity: dispatchTouchEvent: action = ACTION_DOWN
OuterViewGroup: dispatchTouchEvent: action = ACTION_DOWN
InnerViewGroup: dispatchTouchEvent: action = ACTION_DOWN
MyView: dispatchTouchEvent: action = ACTION_DOWN
MyView: onTouchEvent: action = ACTION_DOWN

TestActivity: dispatchTouchEvent: action = ACTION_MOVE
OuterViewGroup: dispatchTouchEvent: action = ACTION_MOVE
InnerViewGroup: dispatchTouchEvent: action = ACTION_MOVE

TestActivity: dispatchTouchEvent: action = ACTION_UP
OuterViewGroup: dispatchTouchEvent: action = ACTION_UP
InnerViewGroup: dispatchTouchEvent: action = ACTION_UP
MyView: dispatchTouchEvent: action = ACTION_UP
MyView: onTouchEvent: action = ACTION_UP
复制代码
事件分发流程图

在 dispatchTouchEvent 中拦截 ACTION_MOVE 事件的流程图如下。

在 dispatchTouchEvent 中拦截 ACTION_MOVE.png

ACTION_DOWN 事件的传递

因为没有对 ACTION_DOWN 事件做任何的拦截,所以 ACTION_DOWN 消息传递到了 MyView

ACTION_MOVE 事件的传递

因为在 ViewGroup2 的 dispatchTouchEvent 中拦截了 ACTION_MOVE,所以 ACTION_MOVE 传递到 ViewGroup2 就停止了。和在 ViewGroup2 的 onInterceptTouchEvent 拦截 ACTION_MOVE 不一样,MyView 后续并没有收到 ACTION_CANCEL 消息。

ACTION_UP 事件的传递

因为没有对 ACTION_UP 事件做任何的拦截,所以 ACTION_DOWN 消息传递到了 MyView

总结

  1. 一般可以在 onInterceptTouchEvent 和 dispatchTouchEvent 中拦截 ACTION_MOVE 消息,但要确保 ACTION_MOVE 消息可以传递到拦截处。
  2. 当你想要处理 ACTION_MOVE 消息时,有经验的工程师会告诉你在控件的 onTouchEvent 中返回 true 就可以了,从第 3 节可以知道,ACTION_MOVE 是可以传递到该控件的。但是要注意该控件的外层没有拦截掉 ACTION_MOVE。

到此,本文就全部结束了,相信读者对 View 的事件分发一定有了新的理解。

写在最后

如果你对我感兴趣,请移步到 blogss.cn ,或关注公众号:程序员小北,进一步了解。

  • 如果本文帮助到了你,欢迎点赞和关注,这是我持续创作的动力 ❤️
  • 由于作者水平有限,文中如果有错误,欢迎在评论区指正 ✔️
  • 本文首发于掘金,未经许可禁止转载 ©️

Supongo que te gusta

Origin juejin.im/post/7067698735874539527
Recomendado
Clasificación