View 事件分发规律总结(超详细)

上一篇 事件分发—初体验文章中实现了一个能够滑动关闭的 Demo,主要来体验一下事件分发,这篇来对 View 的事件分发做一下规律总结,包括【单一 View】,【单一 ViewGroup(不含子 View)】,【ViewGroup + View】,【ViewGroup + ViewGroup】。

1. 事件分发总览

当用户点击屏幕产生一个动作,这个动作通过底层硬件来捕获,然后交给 ViewRootImpl,接着将事件传递给 DecorView,DecorView 再交给 PhoneWindow,PhoneWindow 再交给Activity,然后接下来就是我们常见的 View 事件分发。

硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity

先来看一张图,看一下事件分发的规则

在这里插入图片描述

我们知道常见的控制事件传递的 3 个回调函数,dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,其中 onInterceptTouchEvent 函数是 ViewGroup 所有。

(1) dispatchTouchEvent(针对 ACTION_DOWN)

事件从 Activity 开始,当 Activity 中的 dispatchTouchEvent 返回 true 和 false,均会消耗事件,事件不再向下传递,只有 super 时才会向下传递。super 这个返回操作实际上对于 ViewGroup 和 View 也是类似,ViewGroup 中,dispatchTouchEvent 返回使用 super 时,会交给 onInterceptTouchEvent,也相当于向下传递。View 中 dispatchTouchEvent 返回使用 super 时,会传递给 onTouchEvent,同样类似于向下传递,所有可以总结出 dispatchTouchEvent 中的返回 super,相当于向下分发,自己不消耗,一路 super 下去,就会形成一个事件分发的 U 型图。对于返回 true 时,很好记忆,直接消耗掉事件,不再分发也不向上回溯,后续的事件也会传到该位置。对于返回值 为 false 时,从图中可以看出,Activity 中直接消耗事件,事件不再向下传递,对于 ViewGroup 和 View,事件会回溯到父 View 或者 Activity 中,后续的事件不再传到这里。

(2) onInterceptTouchEvent,是 ViewGroup 中的方法,具有导流的作用,事件从自己的 dispatchTouchEvent (通过 super 返回)分发过来,它的返回值为 super 或者 false 时,表示不拦截事件,事件会传递到子 View 中。当返回值为 true 时,表示拦截事件,会将事件交给自己的 onTouchEvent 处理,一旦拦截,后续的事件不会再经过 onInterceptTouchEvent。拦截后分两种情况,一种是 onTouchEvent 返回 true,则后续事件直接交给 onTouchEvent 处理,不经过 onInterceptTouchEvent;另一种情况是 onTouchEvent 返回 false 或者 super,则后续不在传递到该 ViewGroup,自然也就不在经过 onInterceptTouchEvent。

(3) onTouchEvent,一般具体的执行动作会在这个方法中处理,在这个方法中,对 ACTION_DOWN 事件的处理尤为关键,当动作为 ACTION_DOWN 时,返回 true,表示事件分发到此结束,事件不再向下或者向上传递,同时后续事件 ACTION_MOVE 和 ACTION_UP 也会传到这里。当动作为 ACTION_DOWN 时,返回 false 时,事件会回溯到父 View 或者 Activity 中,后续事件不再交给此 View 处理。ACTION_MOVE 和 ACTION_UP 中的返回值仅仅影响父 View 或者 Activity 中能否收到 对应的事件,如在 ACTION_MOVE 返回 super 或者 false 时,父 View 或者 Activity 也会同样收到 move 事件,不影响后续事件分发,返回值 为 true 时,父 View 或者 Activity 不会收到 move 事件,对于 ACTION_UP 同样。

对于事件分发,一般更多的操作都是在 onInterceptTouchEvent、onTouchEvent,很少在 dispatchTouchEvent 做处理,dispatchTouchEvent 中处理也相对比较简单,因为它是一个事件的入口,消耗就自己处理,不消耗就交给父 View/ Activity,或者交给 onInterceptTouchEvent。所以我们主要对 onInterceptTouchEvent、onTouchEvent 来分析总结,对这几种组合总结 【单一 View】,【单一 ViewGroup(不含子 View)】,【ViewGroup + View】,【ViewGroup + View】。

2.单一 View

自定义一个 View,主要用来打印 log。

class MyView1 @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    View(context, attrs, defStyleAttr) {

    companion object {
        const val TAG = "MyView1"
    }

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        Log.e(TAG, "--- > dispatchTouchEvent --- > ${convertActivon2Name(ev.action)}")
        // (1)自己消耗事件
//        return true
        // (2)自己不消耗事件,回溯到父 View 中的 onTouchEvent
//        return false
        // (3)自己不消耗,传给自己的 onTouchEvent
        return super.dispatchTouchEvent(ev)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        var result = false
        when (event.action) {
            // 事件为 false,后续事件都不会传递给 MyView1,ACTION_MOVE 和 ACTION_UP 中 true 或 false 不影响事件传过来
            // 所以 ACTION_DOWN 事件才是决定的关键,起到导流的作用
            MotionEvent.ACTION_DOWN -> {
                Log.e(TAG, "--- > onTouchEvent -- ${convertActivon2Name(event.action)}")
                result = true
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e(TAG, "--- > onTouchEvent -- ${convertActivon2Name(event.action)}")
                result = false
            }
            MotionEvent.ACTION_UP -> {
                Log.e(TAG, "--- > onTouchEvent -- ${convertActivon2Name(event.action)}")
                result = true
            }
        }
        return result
        /**
         * (1)自己消耗事件
         * return true
         *
         * (2)自己不消耗事件,回溯到父 View 中的 onTouchEvent
         * return false
         *
         * (3)自己不消耗,回溯到父 View 中的 onTouchEvent
         * return super.onTouchEvent(event)
         */

    }
}

然后在布局文件中直使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/gray"
        tools:context=".SingleViewActivity">

    <com.ralf.vieweventtest1.MyView1
            android:id="@+id/single_view"
            android:layout_margin="40dp"
            android:background="@color/colorPrimary"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

</LinearLayout>

在MyView上按下触屏并滑动

(1) MyView1 - onTouchEvent - ACTION_DOWN 中返回 false

1970-01-02 14:22:43.123 5023-5023/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_DOWN
1970-01-02 14:22:43.124 5023-5023/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_DOWN
1970-01-02 14:22:43.126 5023-5023/com.ralf.vieweventtest1 E/SingleViewActivity: --- > onTouchEvent -- ACTION_DOWN
1970-01-02 14:22:43.152 5023-5023/com.ralf.vieweventtest1 E/SingleViewActivity: --- > onTouchEvent -- ACTION_MOVE
1970-01-02 14:22:43.157 5023-5023/com.ralf.vieweventtest1 E/SingleViewActivity: --- > onTouchEvent -- ACTION_MOVE
1970-01-02 14:22:43.157 5023-5023/com.ralf.vieweventtest1 E/SingleViewActivity: --- > onTouchEvent -- ACTION_UP

可以看到 onTouchEvent 在事件为 ACTION_DOWN 返回 false 时,只会收到第一次的 down 事件,后续的 move、up 事件都不再派发给 MyView1,事件均交给父 View 或者 Activity 处理,这里仅仅用 Activity 来显示。

(2) MyView1 - onTouchEvent - ACTION_DOWN 中返回 true

1970-01-02 14:28:27.480 5527-5527/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_DOWN
1970-01-02 14:28:27.481 5527-5527/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_DOWN
1970-01-02 14:28:27.503 5527-5527/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_MOVE
1970-01-02 14:28:27.503 5527-5527/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_MOVE
1970-01-02 14:28:27.556 5527-5527/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_UP
1970-01-02 14:28:27.556 5527-5527/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_UP

可以看到在第一次 down 事件过来时,返回 true,则后续事件 move 和 up 均会传到 MyView1,而 move 和 up 中的事件的返回值仅仅影响事件能否被父 view 或者 Activity 收到,不会影响事件后续事件传到 MyView1。

ACTION_MOVE 中返回 true, ACTION_UP 中返回 false 或者 super

1970-01-02 14:21:03.625 12865-12865/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_DOWN
1970-01-02 14:21:03.626 12865-12865/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_DOWN
1970-01-02 14:21:03.650 12865-12865/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_MOVE
1970-01-02 14:21:03.650 12865-12865/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_MOVE
1970-01-02 14:21:03.650 12865-12865/com.ralf.vieweventtest1 E/SingleViewActivity: --- > onTouchEvent -- ACTION_MOVE
1970-01-02 14:21:03.660 12865-12865/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_UP
1970-01-02 14:21:03.660 12865-12865/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_UP

ACTION_MOVE 中返回 true, ACTION_UP 中返回 false, SingleViewActivity 中不会收到 move 事件,会收到 up 事件

1970-01-02 14:33:10.218 5783-5783/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_DOWN
1970-01-02 14:33:10.219 5783-5783/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_DOWN
1970-01-02 14:33:10.235 5783-5783/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_MOVE
1970-01-02 14:33:10.236 5783-5783/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_MOVE
1970-01-02 14:33:10.241 5783-5783/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_MOVE
1970-01-02 14:33:10.242 5783-5783/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_MOVE
1970-01-02 14:33:10.242 5783-5783/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_UP
1970-01-02 14:33:10.243 5783-5783/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_UP
1970-01-02 14:33:10.243 5783-5783/com.ralf.vieweventtest1 E/SingleViewActivity: --- > onTouchEvent -- ACTION_UP

ACTION_MOVE 中返回 false, ACTION_UP 中返回 true, SingleViewActivity 中不会收到 up 事件,会收到 move 事件

1970-01-02 14:34:41.769 6053-6053/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_DOWN
1970-01-02 14:34:41.769 6053-6053/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_DOWN
1970-01-02 14:34:41.853 6053-6053/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_MOVE
1970-01-02 14:34:41.853 6053-6053/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_MOVE
1970-01-02 14:34:41.853 6053-6053/com.ralf.vieweventtest1 E/SingleViewActivity: --- > onTouchEvent -- ACTION_MOVE
1970-01-02 14:34:41.855 6053-6053/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_UP
1970-01-02 14:34:41.855 6053-6053/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_UP

好,到这里总结一下,对于单一 View 事件分发处理比较简单,主要看在 ACTION_DOWN 中事件拦截情况,返回true 时,后续事件也会传到这里,如果返回 false 或者 super,那么后续事件不会交给该 View 处理,会交给父 view 或者 Activity 处理。

在这里插入图片描述

3.单一 ViewGroup(不含子 View)

这里定义一个 MyLayout1,用来看下打印的日志,代码中有各种情况的注释,下面也会对各种情况做详细的说明。

class MyLayout1 @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    FrameLayout(context, attrs, defStyleAttr) {

    companion object {
        const val TAG = "MyLayout - 1"
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        // (1)自己消耗事件
//        return true
        // (2)自己不消耗事件,回溯到父 View 或者 Activity 中的 onTouchEvent
//        return false
        // (3)自己不消耗,传给自己的 onTouchEvent
        return super.dispatchTouchEvent(ev)
    }

    /**
     * return super.onInterceptTouchEvent(ev) 和 return false 的效果是一样的,不拦截事件,事件会传递到子 View
     */
    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        Log.e(MyLayout1.TAG, "--- > onInterceptTouchEvent -- ${convertActivon2Name(ev.action)}")
        var result = false
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                // (1)false,不拦截事件,事件会传递到子 View
                // a. 如果子 View 拦截 down 事件(可点击或者在子 view 的 onTouchEvent 中返回 true)
                // 则后续的 move、up 等事件都将先传递到 ViewGroup 的 onInterceptTouchEvent 的方法
                // b. 如果没有子 View 拦截 down 事件
                // 则 down 事件将交由该 ViewGroup 的 onTouchEvent 来处理,后续事件都不再走 onInterceptTouchEvent
                // (2)true,事件拦截,事件会交给自己的 onTouchEvent 处理,
                // 无论 onTouchEvent 是否返回 true 都不再走 onInterceptTouchEvent,并且事件不会再向下传递给子 View
                result = false
            }
            MotionEvent.ACTION_MOVE -> {
                // (3)只有上面的 down 事件中为 false 时,才有可能走这里。有可能的原因:看子 view 的处理,有子 View 并且子 View 拦截 down 事件
                // a. false 不拦截,move 事件会流向子 View,子 View 中 onTouchEvent 中为 true,
                // 则后续的 move、up 等事件都将先传递到 ViewGroup 的 onInterceptTouchEvent 的方法
                // 子 View 中 onTouchEvent 中为 false,事件会回溯到 MyLayout1 父 View 或者 Activity 中的 onTouchEvent,MyLayout1 不接收,MyLayout1 只接收来自 onInterceptTouchEvent 拦截的事件
                // b. true 拦截,move 事件交给自己的 onTouchEvent 来处理, 后续 move 和 up 事件不经过 onInterceptTouchEvent,直接传给 MyLayout1 的 onTouchEvent,
                // move 和 up 的返回值影响父 View 或者 Activity 能否收到响应的事件
                result = true
            }
            MotionEvent.ACTION_UP -> {
                // (4) 只有 onInterceptTouchEvent 中 move 不拦截,
                // 并且子 View 中 onTouchEvent 中 down 事件为 true,up 事件才会走这里
                // a. true,事件【不会】传递到 MyLayout1 的 onTouchEvent,子 View 的 dispatchTouchEvent 中收到 ACTION_CANCEL,
                // MyLayout1 的 父 View 或者 Activity 会收到 up 事件
                // b. false 交给子 view 处理
                result = false
            }
        }
        return result
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {

        var result = false
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.e(MyLayout1.TAG, "--- > onTouchEvent -- ${convertActivon2Name(event.action)}")
                // onInterceptTouchEvent 拦截 down 事件或者事件从子 view 回溯回来:
                // (1) true,后续事件 move 和 up 直接传到这里,不再通过 onInterceptTouchEvent
                // (2) false,事件会回溯到父 View 或者 Activity,后续事件不再传给到 MyLayout1
                result = false
            }
            /**
             * down 事件返回值直接影响 move 和 up,down 为 false,
             * 后续事件都会收不到,为 true,会收到 move 和 up,move 事件中的返回值不会影响 up 事件的接收
             */
            MotionEvent.ACTION_MOVE -> {
                Log.e(MyLayout1.TAG, "--- > onTouchEvent -- ${convertActivon2Name(event.action)}")
                // 能到这里有 2 种情况
                // a. 由自己的 onTouchEvent 中 down 事件为 true,后续事会直接传到这里
                // b. onInterceptTouchEvent 不拦截 down 事件,子 View 拦截 down 事件,onInterceptTouchEvent 中拦截 move 事件
                // true 和 false 只影响 move 能否到达父 View 或者 Activity,不影响 up 事件的接收
                result = false
            }
            MotionEvent.ACTION_UP -> {
                Log.e(MyLayout1.TAG, "--- > onTouchEvent -- ${convertActivon2Name(event.action)}")
                // 能到这里只有一种情况
                // a.由自己的 onTouchEvent 中 down 事件为 true
                result = false
            }
        }
        return result
    }
}

单一 ViewGroup 布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:gravity="center"
        android:layout_height="match_parent"
        android:background="@color/gray"
        tools:context=".SingleViewGroupActivity">

    <com.ralf.vieweventtest1.MyLayout1
            android:id="@+id/single_view_group"
            android:layout_width="match_parent"
            android:layout_marginStart="20dp"
            android:layout_marginEnd="20dp"
            android:layout_height="400dp"
            android:background="@color/colorPrimary">

    </com.ralf.vieweventtest1.MyLayout1>

</LinearLayout>

由于是 ViewGroup,所以就多了 onInterceptTouchEvent 方法,对于单一 ViewGroup, onInterceptTouchEvent 在 down 事件到来时:

(1) 如果拦截(返回 true),那么 down 事件传到 Mylayout1 的 onTouchEvent 中

(2) 如果不拦截(返回 false 或者 super),此时因为没有子 View,事件也会交给 onTouchEvent 处理

所以此时的 Mylayout1 相当于一个单一 View,自然事件的分发情况就和上面单一 View 的情况是一致的

在这里插入图片描述

4.ViewGroup + View

对于 ViewGroup 嵌套 View 的情况, 用我们前面自定义的两个 View,MyView1 和 Mylayout1

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/gray"
        tools:context=".ViewGroupAndViewActivity">

    <com.ralf.vieweventtest1.MyLayout1
            android:id="@+id/mylayout1"
            android:layout_width="match_parent"
            android:background="@color/colorPrimary"
            android:layout_margin="40dp"
            android:layout_height="match_parent">

        <com.ralf.vieweventtest1.MyView1
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="40dp"
                android:layout_gravity="center"
                android:background="@color/colorAccent"/>

    </com.ralf.vieweventtest1.MyLayout1>
</LinearLayout>

ViewGroup 中嵌套 View 的情况比较多,这里先给出一张看似凌乱的图,我们揪着这张图来分析。

在这里插入图片描述

(1) MyLayout1 中 onInterceptTouchEvent 拦截 down 事件

此时 MyView1 不会收到事件,事件会传递到 Mylayout1 中的 onTouchEvent,onTouchEvent 中对事件的处理情况和单一 View 的处理保持一致。此时的就是图中左边的一根蓝线,事件从 onInterceptTouchEvent 转到 Mylayout1 中的 onTouchEvent

下面给出其中一种情况的 log,onInterceptTouchEvent 拦截 down 事件,Mylayout1 中的 onTouchEvent 拦截 down 事件,不拦截 move 和 up 事件,此时 VGAndViewActivity 中可以收到 move 和 up 事件。

1970-01-04 06:26:31.626 9967-9967/com.ralf.vieweventtest1 E/MyLayout - 1: --- > onInterceptTouchEvent -- ACTION_DOWN
1970-01-04 06:26:31.628 9967-9967/com.ralf.vieweventtest1 E/MyLayout - 1: --- > onTouchEvent -- ACTION_DOWN
1970-01-04 06:26:31.651 9967-9967/com.ralf.vieweventtest1 E/MyLayout - 1: --- > onTouchEvent -- ACTION_MOVE
1970-01-04 06:26:31.651 9967-9967/com.ralf.vieweventtest1 E/VGAndViewActivity: --- > onTouchEvent -- ACTION_MOVE
1970-01-04 06:26:31.724 9967-9967/com.ralf.vieweventtest1 E/MyLayout - 1: --- > onTouchEvent -- ACTION_MOVE
1970-01-04 06:26:31.724 9967-9967/com.ralf.vieweventtest1 E/VGAndViewActivity: --- > onTouchEvent -- ACTION_MOVE
1970-01-04 06:26:31.725 9967-9967/com.ralf.vieweventtest1 E/MyLayout - 1: --- > onTouchEvent -- ACTION_UP
1970-01-04 06:26:31.725 9967-9967/com.ralf.vieweventtest1 E/VGAndViewActivity: --- > onTouchEvent -- ACTION_UP

(2) MyLayout1 中 onInterceptTouchEvent 不拦截 down 事件,MyView1 不拦截 down 事件

根据文章开头的事件分发概览图,可以知道,此时事件从 MyView1 的 onTouchEvent 回溯到 Mylayout1 中的 onTouchEvent,这种情况的处理和 (1) 的情况一致。此时也是由图中的右边的两条蓝色线体现,子 View 不拦截事件,事件会交给父 View 的 onTouchEvent 处理。

(3) MyLayout1 中 onInterceptTouchEvent 不拦截 down 事件,MyView1 拦截 down 事件

此时,down 事件到达 MyView1 的 onTouchEvent 中,后续的 move 和 up 事件还要看 onInterceptTouchEvent 中是否做拦截,因为后续事件会先经过 onInterceptTouchEvent。这里也分三种情况来讨论。

a. onInterceptTouchEvent 不拦截 move 和 up 事件

此时 move 事件会传递到 MyView1 的 onTouchEvent 中,MyView1 可以收到 move 事件,对应图中的粉色线,onInterceptTouchEvent 中 ACTION_MOVE 不拦截。那么在 MyView1 的 onTouchEvent 处理 move 和 up 事件时,返回值仅仅影响除 MyLayout1 以外的父 View 或者 Activity

  • 如果返回 true 事件不再传递,事件到此截止。
  • 如果返回 false,事件会再传递给 MyLayout1 的父 View 或者 Activity,但 MyLayout1 尽管是父 View,却收不到事件
1970-01-04 07:19:32.297 11314-11314/com.ralf.vieweventtest1 E/MyLayout - 1: --- > onInterceptTouchEvent -- ACTION_DOWN
1970-01-04 07:19:32.298 11314-11314/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_DOWN
1970-01-04 07:19:32.298 11314-11314/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_DOWN
1970-01-04 07:19:32.318 11314-11314/com.ralf.vieweventtest1 E/MyLayout - 1: --- > onInterceptTouchEvent -- ACTION_MOVE
1970-01-04 07:19:32.318 11314-11314/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_MOVE
1970-01-04 07:19:32.318 11314-11314/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_MOVE
1970-01-04 07:19:32.318 11314-11314/com.ralf.vieweventtest1 E/VGAndViewActivity: --- > onTouchEvent -- ACTION_MOVE
1970-01-04 07:19:32.331 11314-11314/com.ralf.vieweventtest1 E/MyLayout - 1: --- > onInterceptTouchEvent -- ACTION_UP
1970-01-04 07:19:32.331 11314-11314/com.ralf.vieweventtest1 E/MyView1: --- > dispatchTouchEvent --- > ACTION_UP
1970-01-04 07:19:32.332 11314-11314/com.ralf.vieweventtest1 E/MyView1: --- > onTouchEvent -- ACTION_UP
1970-01-04 07:19:32.332 11314-11314/com.ralf.vieweventtest1 E/VGAndViewActivity: --- > onTouchEvent -- ACTION_UP

从 Log 中可以看出,onInterceptTouchEvent 不拦截 move 和 up 事件,MyView1 中是可以收到后续的 move 和 up 事件,返回值仅仅影响除 MyLayout1 以外的父 View 或者 Activity

b. onInterceptTouchEvent 拦截 move 事件

如果 Mylayout1 在 onInterceptTouchEvent 中拦截 move 事件,此时事件就不再传递给 MyView1 了,而是由自己来处理,也就是说 MyLayout1 的 onTouchEvent 会收到后续的 move 和 up 事件,这也符合 onInterceptTouchEvent 的拦截特点,事件一旦拦截,后续事件都交给它来处理,同时,后续事件到来时,就不再经过 onInterceptTouchEvent,直接交给 MyLayout1 的 onTouchEvent 处理,对应图中左边黄色线。然后在 onTouchEvent 中事件的返回值影响 MyLayout1 的父 View 或者 Activity 能否收到 move 和 up 事件,true 表示截断事件,事件不再传递,所以收不到;false 不截断事件,可以收到事件

c. onInterceptTouchEvent 拦截 up 事件

事件能到这里,意味着上面 move 事件没有拦截。此时拦截 up 事件,猜想 up 事件会交给 MyLayout1 的 onTouchEvent 处理,但实际上 MyLayout1 的 onTouchEvent 中并没有收到事件,这里猜测可能不符合逻辑吧,只有手指在屏幕上有 move 事件时,然后才有手指抬起的 up 动作。具体的原因在源码分析时再去找答案,这里先记住总结。

那么拦截 up 事件时,会有哪些效果呢?首先在 MyView1 的 dispatchTouchEvent 中收到了一个动作,ACTION_CANCEL,源码中给出了说明,后面这个 View 不再收到事件,相当于一个 up 事件,因为 up 事件是事件的结束动作。总之,MyView1 不再收到任何动作。所以 onInterceptTouchEvent 拦截 up 事件,使得子 View 中收不到 up 事件,但是 MyLayout1 的父 View 或者 Activity 能收到 up 事件。对应图中很特殊的一根线,红色线段。

/**
 * Constant for {@link #getActionMasked}: The current gesture has been aborted.
 * You will not receive any more points in it.  You should treat this as
 * an up event, but not perform any action that you normally would.
 */
public static final int ACTION_CANCEL           = 3;

到这里就将 ViewGroup + View 组合情况的事件分发总结完了,虽然情况有点多,但总结之后也是很容易记住的。

首先 ViewGroup 中 onInterceptTouchEvent 拦截 down 事件和子 View 中不拦截 down 事件,即上面的情况 (1) 和 (2),事件就会交给 ViewGroup 的 onTouchEvent 处理,这种情况就相当于一个单一 View。

第二类情况就是 ViewGroup 中 onInterceptTouchEvent 不拦截 down 事件,子 View 拦截 down 事件,此时子 View 中才有机会收到 move 和 up 事件,因为 move 和 up 事件的接收要看 onInterceptTouchEvent 对事件的处理,onInterceptTouchEvent 中拦截 move,那么后续 move 和 up 事件就交给 ViewGroup 的 onTouchEvent 处理,子 View 不再接收事件。onInterceptTouchEvent 中不拦截 move,子 View 能收到 move 事件,其中返回值也需要注意,true 时自己处理事件,不再向上传递,false 时子 事件会向上传递,但是 onInterceptTouchEvent 中不拦截 move 的父 View(如上面的 MyLayout1) 不接收 move 事件,但是 MyLayout1 的 View 或者 Activity 中可以收到 move 事件。还有一种情况就是 onInterceptTouchEvent 中拦截 up,拦截后自己不接收 up 事件,子 View 中不接收 up 事件,父 View 或者 Activity 中可以收到 up 事件。

这里再给出一张脑图,对于各种情况的总结会更加直观。

在这里插入图片描述

这里还有一个小细节,当 onInterceptTouchEvent 中拦截 move 时,首次 move 事件过来时, MyView1 的 dispatchTouchEvent 中收到了一个动作 ACTION_CANCEL,MyLayout1 中并没有收到第一次的 move 事件,而 Activity 中收到了第一次的 move 事件,从 log 中可以看出,VGAndViewActivity 中第一次 move 动作的时间比 MyLayout1 的第一次 move 动作要早,即 MyLayout1 中 move 动作应该是第二次到来的 move。这个小细节也留到源码分析时去找答案。

在这里插入图片描述

5.ViewGroup + ViewGroup

对于这种情况的处理,基本上和上面 ViewGroup + View 的情况类似。

(1) MyLayout1 拦截 down 事件,事件都会交给 MyLayout1 的 onTouchEvent 处理,MyLayout2 不会收到事件

(2) Mylayout1 不拦截 down 事件,此时 MyLayout2 相当于一个单一 ViewGroup:

MyLayout2 中 onInterceptTouchEvent 中拦截 down 事件和不拦截 down 事件,事件都会交给 MyLayout2 的 onTouchEvent 处理,此时 MyLayout2 就是一个 View,此时就相当于 ViewGroup + View,按照上面 ViewGroup + View 的处理即可。

到这里,View 事件分发规律总结就完了,可以看出其实事件分发也并没有那么难,只要细心分析上述的几种情况,就已经将各种事件的处理涵盖全面了,日常开发中可能会有很多 View 的组合情况,按照合理拆分和要求,就会很容易完成事件的分发了。本篇文章是对各种事件分发情况的一种总结,并没有源码分析,有些细节问题到源码分析时再去寻求答案。

参考、练习代码

图解 Android 事件分发机制

Android Touch事件传递机制(一) – onInterceptTouchEvent & onTouchEvent

代码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值