上一篇 事件分发—初体验文章中实现了一个能够滑动关闭的 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)自己不消耗,传给自己的 onTouchEventreturn super.dispatchTouchEvent(ev)}override fun onTouchEvent(event: MotionEvent): Boolean {var result = falsewhen (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"?>
<LinearLayoutxmlns: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.MyView1android: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)自己不消耗,传给自己的 onTouchEventreturn 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 = falsewhen (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,并且事件不会再向下传递给子 Viewresult = 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 = falsewhen (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,后续事件不再传给到 MyLayout1result = 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 事件为 trueresult = false}}return result}
}

单一 ViewGroup 布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns: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.MyLayout1android: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"?>
<LinearLayoutxmlns: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.MyLayout1android:id="@+id/mylayout1"android:layout_width="match_parent"android:background="@color/colorPrimary"android:layout_margin="40dp"android:layout_height="match_parent"><com.ralf.vieweventtest1.MyView1android: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

代码地址

View 事件分发规律总结(超详细)相关推荐

  1. Android面试老生常谈的 View 事件分发机制,看这一篇就够了

    本文首发我的微信公众号:徐公,想成为一名优秀的 Android 开发者,需要一份完备的 知识体系,在这里,让我们一起成长,变得更好~. 在 Android 开发当中,View 的事件分发机制是一块很重 ...

  2. android 点击事件消费,Android View事件分发和消费源码简单理解

    Android View事件分发和消费源码简单理解 前言: 开发过程中觉得View事件这块是特别烧脑的,看了好久,才自认为看明白.中间上网查了下singwhatiwanna粉丝的读书笔记,有种茅塞顿开 ...

  3. 一文读懂Android View事件分发机制

    Android View 虽然不是四大组件,但其并不比四大组件的地位低.而View的核心知识点事件分发机制则是不少刚入门同学的拦路虎.ScrollView嵌套RecyclerView(或者ListVi ...

  4. View事件分发机制(源码 API27)

    1.什么是事件分发机制 当用户触摸屏幕时,会产生一个touch事件,这个touch事件(motionEvent)传递到某个具体的view处理的整个过程 用户触摸屏幕会产生一个事件流(ACTION_DO ...

  5. View事件分发机制(源码分析篇)

    01.Android中事件分发顺序 1.1 事件分发的对象是谁 事件分发的对象是事件.注意,事件分发是向下传递的,也就是父到子的顺序. 当用户触摸屏幕时(View或ViewGroup派生的控件),将产 ...

  6. 【Android View事件分发机制】关于拦截事件的注意点

    在父容器拦截事件时,为什么不能拦截DOWN事件呢? 先看看源码: 回顾一下事件分发机制原理,当事件来了之后,如果父容器不拦截,则会询问其child view ,当某child view 有事件需求,父 ...

  7. 【Android View事件分发机制】滑动冲突

    View内容滑动概念 scrollTo scrollBy scrollTo(x,y) x,y 是绝对值,如果x,y不变,重复调用是不会移动的. scrollBy(x,y) x,y是增量之,每次调用都会 ...

  8. 【Android View事件分发机制】原理

    事件体系中的几个基础类 MotionEvent 点击事件的封装. getX/Y 相当于当前View左上角的x,y坐标 getRawX/Y 相对于手机屏幕左上角的x,y坐标 GestureDetecto ...

  9. Android View 事件分发机制详解

    想必很多android开发者都遇到过手势冲突的情况,我们一般都是通过内部拦截和外部拦截法解决此类问题.要想搞明白原理就必须了解View的分发机制.在此之前我们先来了解一下以下三个非常重要的方法: di ...

最新文章

  1. Spring越来越强,而我们越来越快餐!离开了Spring,居然API都写不出来了! 程序猿DD...
  2. 洛谷——P1014 Cantor表
  3. python基础之socket编程
  4. 细节决定成败:一个公共类库
  5. 在基于简单Vertx Rest的应用程序上为REST资源设置基本响应HTTP标头
  6. android已停止三星,急,android离线打包程序三星下总报 程序 已停止
  7. 测试开发——flask视图函数与路由 实战重点
  8. 基于python和opencv的人脸识别
  9. mysql视图可以完成的操作_MySQL视图操作
  10. 烧写树莓派系统,SSH配置,无屏登录流程
  11. 【预测模型】基于天牛须算法优化ELman神经网络实现数据预测matlab代码
  12. 实现通过Xcode安装到虚拟机后安装iPhone模拟器并安装ipa软件到模拟器上
  13. C# winform excel根据当前选中内容,自动插入/编辑批注
  14. PID控制器的离散化推导及其C语言实现
  15. 使用adb指令往机顶盒上安装应用
  16. 阿里云ECS服务器跨账号迁移
  17. 2018年世界杯冠军竟然被大数据算出来了,还要比吗?
  18. 搜狗拼音皮肤 php文件,搜狗拼音输入法皮肤制作
  19. ZOJ ——3629 Treasure Hunt IV
  20. 联想型号启天M420-N000台式机进入bios设置u盘启动

热门文章

  1. 屈光性白内障治疗手术案例
  2. 63家企业上榜!华为云优秀合作伙伴公布!
  3. php985工程,985大学名单排名最新
  4. 华为、苹果、三星扎堆发财报,谁的日子最不好过?
  5. Webbench源码分析之多进程(三)
  6. 2022年终总结:给心一个立住的理由【失败后自我恢复、压力中自我拯救】
  7. 连线:iPhone研发不为人知的故事 原型机纰漏百出
  8. 考研复试英语介绍计算机专业,2018计算机考研复试英语自我介绍范本及重点
  9. 加州大学伯克利分校:MapReduce Spark
  10. python3简明教程第二版答案_Python3 简明教程