Android事件分发机制
一、概述
1. 事件
事件通常指触摸或点击事件,用户触摸屏幕时产生 Touch
事件。Touch
事件的相关细节封装于 MotionEvent
对象中。
事件类型 | 具体动作 |
---|---|
MotionEvent.ACTION_DOWN | 按下事件(开始) |
MotionEvent.ACTION_UP | 抬起事件(结束) |
MotionEvent.ACTION_MOVE | 滑动事件 |
MotionEvent.ACTION_CANCEL | 取消事件 |
2. 分发流程

如上图所示,onTouch事件产生后,先传给Activity,再传给View Group,最后传给View。
事件分发流程的目的就是要找到第一个要处理事件的对象。一旦有一个对象消费了该事件,事件分发结束。反之,如果事件没有被消费,则会被废弃。
3. 重要方法
方法 | 作用 |
---|---|
dispatchTouchEvent(event: MotionEvent?): Boolean | 进行事件分发 |
onInterceptTouchEvent(event: MotionEvent?): Boolean | 进行事件拦截 |
onTouchEvent(event: MotionEvent?): Boolean | 进行事件消耗 |
三个方法之间的关系可以使用如下伪代码表示:
fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
val consume = false
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev)
} else {
consume = child.dispatchTouchEvent(ev)
}
return consume
}
事件的传递规则:对于ViewGroup,点击事件传递过来后,首先调用 dispatchTouchEvent
方法。如果其 onInterceptTouchEvent
方法返回true,表示拦截该事件,随后它的 onTouch
方法被调用;如果 onInterceptTouchEvent
方法返回false,表示不拦截事件,该事件会继续传递给子View,接着子View的 dispatchTouchEvent
方法被调用。重复该过程直至事件被消耗。
二、Activity的事件分发
1. Demo演示
(1) 重写Activity的 dispatchTouchEvent
和 onTouchEvent
方法。
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "Activity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
val eventName = EventUtil.getActionName(ev)
LogUtil.i(TAG, "dispatchTouchEvent $eventName Start", LogUtil.Depth.ACTIVITY)
val result = super.dispatchTouchEvent(ev)
LogUtil.i(TAG, "dispatchTouchEvent $eventName End with $result", LogUtil.Depth.ACTIVITY)
return result
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val eventName = EventUtil.getActionName(event)
LogUtil.i(TAG, "onTouchEvent $eventName", LogUtil.Depth.ACTIVITY)
return super.onTouchEvent(event)
}
}
(2) 自定义MyLayout (继承自FrameLayout) 并重写 dispatchTouchEvent方法
。
class MyLayout : FrameLayout {
companion object {
const val TAG = "MyLayout"
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
val eventName = EventUtil.getActionName(ev)
val result = false // false or true
LogUtil.i(TAG, "dispatchTouchEvent $eventName End with $result", LogUtil.Depth.VIEW_GROUP)
return result
}
}
(3) activity_main.xml
<com.example.eventdispatch.ui.MyLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"
android:background="@color/colorPrimary"
android:gravity="center">
</com.example.eventdispatch.ui.MyLayout>

当MyLayout dispatchTouchEvent
返回false时,表示其不对事件进行分发。ACTION_DOWN事件传递到MyLayout时,dispatchTouchEvent
被调用,返回false,事件返回给Activity,Activity的 onTouchEvent
被调用。当ACTION_MOVE或ACTION_UP事件到来时,由于上一个事件由Activity处理,因此该事件不再向下传递,直接交给Activity处理。点击MyLayout,打印的Log如下:
I/Activity: dispatchTouchEvent ACTION_DOWN Start
I/ MyLayout: dispatchTouchEvent ACTION_DOWN End with false
I/Activity: onTouchEvent ACTION_DOWN
I/Activity: dispatchTouchEvent ACTION_DOWN End with false
I/Activity: dispatchTouchEvent ACTION_UP Start
I/Activity: onTouchEvent ACTION_UP
I/Activity: dispatchTouchEvent ACTION_UP End with false
当MyLayout dispatchTouchEvent
返回true时,事件被MyLayout消耗,Activity的 onTouchEvent
不会被调用。点击MyLayout,打印的Log如下:
I/Activity: dispatchTouchEvent ACTION_DOWN Start
I/ MyLayout: dispatchTouchEvent ACTION_DOWN End with true
I/Activity: dispatchTouchEvent ACTION_DOWN End with true
I/Activity: dispatchTouchEvent ACTION_UP Start
I/ MyLayout: dispatchTouchEvent ACTION_UP End with true
I/Activity: dispatchTouchEvent ACTION_UP End with true
2. 源码分析
注: 本文所有源码为API Level 29
点击事件产生后,最先传递给当前Activity,Activity的 dispatchTouchEvent
方法被调用。
Activity的 dispatchTouchEvent
方法如下:
/**
* Acticity.java
* Line 3989-3997
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 空方法
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
可以看到,事件首先交给Activity所属的Window进行分发,如果返回true,则事件分发结束;如果返回false,意味着事件没有被处理,Activity的 onTouchEvent
被调用。
getWindow
返回Window对象,Window是一个抽象类,PhoneWindow是其唯一的实现类。因此 getWindow().superDispatchTouchEvent(ev)
就是调用PhoneWindow的 superDispatchTouchEvent(ev)
方法。
PhoneWindow的 superDispatchTouchEvent
方法如下:
/**
* PhoneWindow.java
* Line 1847-1850
*/
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow将事件传递给了DecorView对象mDecor,mDecor是 getWindow().getDecorView()
返回的View,Activity中通过 setContentView
设置的View是它的一个子View。
DecorView的 superDispatchTouchEvent
方法如下:
/**
* DecorView.java
*/
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks{
// ...
// Line464-466
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
DecorView继承自FramgLayout,FrameLayout又继承自ViewGroup,所以 mDecor.superDispatchTouchEvent(event)
其实就是调用ViewGroup的 dispatchTouchEvent
方法。至此,事件已经分发给ViewGroup了。
3. 分发流程图

三、ViewGroup的事件分发
1. Demo演示
(1) 自定义MyLayout (继承自FrameLayout) 并重写 dispatchTouchEvent
方法、onInterceptTouchEvent
方法、onTouchEvent
方法。
class MyLayout : FrameLayout {
companion object {
const val TAG = "MyLayout"
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
val eventName = EventUtil.getActionName(ev)
LogUtil.i(TAG, "dispatchTouchEvent $eventName Start", LogUtil.Depth.VIEW_GROUP)
val result = super.dispatchTouchEvent(ev)
LogUtil.i