前言
网络上有很多关于 View 事件分发机制的文章,我本人也阅读过不少,大部分一上来就是各种结论和源码分析,暂不评价这些分析和结论是否正确,单从这样的出发点去学习 View 的事件分发机制就不恰当。
Android View 的事件分发过程情况多样,本文会带领读者在不看一行源代码的情况下,通过一个实例和十几张事件分发流程图来完整分析 View 的事件分发机制。话不多说,实践是检验真理的唯一标准,让我们开始学习吧。
本文大纲
准备工作
View 的事件分发是由外向内的,总是从父 View 传递给子 View。在这里我写了三个自定义 View 来直观的分析这个过程,这种方式也最好理解。
这三个 View 是 OuterViewGroup
,InnerViewGroup
,MyView
。分别继承自 ViewGroup
,ViewGroup
,View
。对于 OuterViewGroup
,InnerViewGroup
来说,需要重写 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>
最终的显示效果如下。
有读者可能会问,直接用两个 LinearLayout
套一个 Button
不是一样的效果吗,何必多此一举。其实不然,系统自带的这些控件内部很多都对 View 的事件做了拦截(消费)处理,不便于我们得到一个正确的结果,所以自定义 View 继承 ViewGroup 或 View来进行分析,最后的结果一定是最准确、纯净、原始的。
1. 默认的事件分发过程
简单的准备好实验条件后,首先来看一下 View 默认的事件分发流程。默认是指我们不对 View 的任何事件做拦截处理,遵循系统的默认规则。在这种情况下,重写 OuterViewGroup
,InnerViewGroup
,MyView
和 TestActivity
的 dispatchTouchEvent
,onTouchEvent
方法,日志打印出他们的调用顺序,即可得到 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 的 dispatchTouchEvent
,onTouchEvent
方法默认返回值都是 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
对应流程图中的Activity
,OuterViewGroup
对应ViewGroup1
,InnerViewGroup
对应ViewGroup2
,MyView
对应View
。
根据日志的结果,可以画出默认的事件分发流程图如下。
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 后也停止向下传递了。
总结
根据上面的分析,在默认情况下,有如下结论。
对于 ACTION_DOWN 事件,会从最外层的
dispatchTouchEvent
传递到最内层的dispatchTouchEvent
,然后由最内层的onTouchEvent
传递到最外层的onTouchEvent
。对于 ACTION_MOVE 和 ACTION_UP 来说,只会传递到 Activity 的
dispatchTouchEvent
和onTouchEvent
中。
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 中拦截事件的流程图如下。
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 中拦截事件的流程图如下。
ACTION_DOWN 事件的传递
因为在 InnerViewGroup
的 dispatchTouchEvent 拦截了所有事件,所以 ACTION_DOWN 事件只能从 TestActivity
传递到 InnerViewGroup
,而 MyView
接收不到任何事件。
ACTION_MOVE 事件的传递
ACTION_MOVE 从 TestActivity
传递到 InnerViewGroup
,MyView
接收不到任何事件。
ACTION_UP 事件的传递
ACTION_UP 从 TestActivity
传递到 InnerViewGroup
,MyView
接收不到任何事件。
这里读者思考一个问题,如果在 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 中拦截事件的流程图如下。
ACTION_DOWN 事件的传递
ACTION_DOWN 从最外层(TestActivity)传递到了最内层(MyView)。
ACTION_MOVE 事件的传递
ACTION_MOVE 从最外层(TestActivity)传递到了最内层(MyView)。
ACTION_UP 事件的传递
ACTION_UP 从最外层(TestActivity)传递到了最内层(MyView)。
总结
根据上面的分析,在 dispatchTouchEvent 中拦截事件,可以得出如下结论。
- 在
Activity
,ViewGroup
,View
的 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 中拦截事件的流程图如下,和默认的事件分发过程是一样的。
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 中拦截事件的流程图如下。
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 中拦截事件的流程图如下。
ACTION_DOWN 事件的传递
ACTION_DOWN 事件由外向内传递到 MyView,因为在 MyView 的 onTouchEvent 中拦截了事件,所以 ACTION_DOWN 在 MyView 停止向外传递。
ACTION_MOVE 事件的传递
与 ACTION_DOWN 事件传递流程一样。
ACTION_UP 事件的传递
与 ACTION_DOWN 事件传递流程一样。
总结
我们已经分析完了在 dispatchTouchEvent 和 onTouchEvent 中拦截事件的事件传递规则,现在结合两种拦截方式做一个总结,看看这两种拦截方式的内在联系。
可以发现,事件传递遵循由外向内(外层的 dispatchTouchEvent 到内层的 dispatchTouchEvent),然后由内向外(内层的 onTouchEvent 到外层的 onTouchEvent)的顺序。
如果事件在由外向内传递过程中被拦截(消费)了,那么由内向外的过程就不会发生,这也很容易理解。
在 onTouchEvent 中拦截(消费)事件,不影响事件由外向内传递到拦截处的过程。
在 onTouchEvent 中拦截(消费)事件,只影响事件由内向外传递的过程,对于 ACTION_MOVE,其内外层的 onTouchEvent 均不会被调用。
从打印出的日志和流程图可以发现,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 中拦截所有事件的流程图如下。
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 中拦截事件的流程图如下。
ACTION_DOWN 事件的传递
ACTION_DOWN 事件传递到 InnerViewGroup 后停止。
ACTION_MOVE 事件的传递
ACTION_MOVE 事件传递到 InnerViewGroup 后停止。
ACTION_UP 事件的传递
ACTION_UP 事件传递到 InnerViewGroup 后停止。
总结
单纯在 onInterceptTouchEvent 中拦截,ACTION_DOWN 会在拦截处停止传递,而后续的 ACTION_MOVE 只会传递到 Activity。
而如果同时在 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 小节的这张图。
因此重写 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 事件的流程图如下。
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 事件的流程图如下。
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
。
总结
- 一般可以在 onInterceptTouchEvent 和 dispatchTouchEvent 中拦截 ACTION_MOVE 消息,但要确保 ACTION_MOVE 消息可以传递到拦截处。
- 当你想要处理 ACTION_MOVE 消息时,有经验的工程师会告诉你在控件的 onTouchEvent 中返回 true 就可以了,从第 3 节可以知道,ACTION_MOVE 是可以传递到该控件的。但是要注意该控件的外层没有拦截掉 ACTION_MOVE。
到此,本文就全部结束了,相信读者对 View 的事件分发一定有了新的理解。
写在最后
如果你对我感兴趣,请移步到 http://blogss.cn ,或关注公众号:程序员小北,进一步了解。
- 如果本文帮助到了你,欢迎点赞和关注,这是我持续创作的动力 ❤️
- 由于作者水平有限,文中如果有错误,欢迎在评论区指正 ✔️
- 本文首发于掘金,未经许可禁止转载 ©️