View 事件分发规律总结(超详细)
上一篇 事件分发—初体验 文章中实现了一个能够滑动关闭的 Demo,主要来体验一下事件分发,这篇来对 View 的事件分发做一下规律总结,包括【单一 View】,【单一 ViewGroup(不含子 View)】,【ViewGroup + View】,【ViewGroup + ViewGroup】。
1. 事件分发总览
当用户点击屏幕产生一个动作,这个动作通过底层硬件来捕获,然后交给 ViewRootImpl,接着将事件传递给 DecorView,DecorView 再交给 PhoneWindow,PhoneWindow 再交给Activity,然后接下来就是我们常见的 View 事件分发。
硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity
先来看一张图,看一下事件分发的规则
view_event_content.png我们知道常见的控制事件传递的 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 处理。
single_view_event.png3.单一 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 的情况是一致的
single_viewgroup.png4.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 的情况比较多,这里先给出一张看似凌乱的图,我们揪着这张图来分析。
viewgroup_view1.png(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 事件。
这里再给出一张脑图,对于各种情况的总结会更加直观。
viewgroup_view.png这里还有一个小细节,当 onInterceptTouchEvent 中拦截 move 时,首次 move 事件过来时, MyView1 的 dispatchTouchEvent 中收到了一个动作 ACTION_CANCEL,MyLayout1 中并没有收到第一次的 move 事件,而 Activity 中收到了第一次的 move 事件,从 log 中可以看出,VGAndViewActivity 中第一次 move 动作的时间比 MyLayout1 的第一次 move 动作要早,即 MyLayout1 中 move 动作应该是第二次到来的 move。这个小细节也留到源码分析时去找答案。
view_event_log.png5.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 Touch事件传递机制(一) -- onInterceptTouchEvent & onTouchEvent