1. 前言
🎯 一句话总结:
触摸事件(TouchEvent)会从 Activity
层开始,按从外到内的方式传递给每一个 ViewGroup/View,直到某个 View 消费(consume) 它,事件传递就会停止。
📌 事件分发三个关键方法
方法名 | 所在类 | 作用说明 |
---|---|---|
dispatchTouchEvent() |
所有 View/ViewGroup | 事件分发入口,决定是否继续向下传递 |
onInterceptTouchEvent() |
仅 ViewGroup | 是否拦截事件,阻止传递给子 View |
onTouchEvent() |
所有 View/ViewGroup | 事件的最终处理者(消费者) |

DecorView
是一个应用窗口的根容器,它本质上是一个FrameLayout
。DecorView
有唯一一个子View,它是一个垂直LinearLayout
,包含两个子元素,一个是TitleView
(ActionBar的容器),另一个是ContentView
(窗口内容的容器)。
java
Activity.dispatchTouchEvent()
↓
Window.superDispatchTouchEvent()
↓
DecorView.dispatchTouchEvent()
↓
ViewGroup.dispatchTouchEvent()
↓
- onInterceptTouchEvent() → 是否拦截?
↓ ↓
拦截自己处理 不拦截继续往下
↓
子View.dispatchTouchEvent()
↓
View.dispatchTouchEvent()
↓
- onTouchListener.onTouch()
- onTouchEvent()
Activity.dispatchTouchEvent()
- 触摸事件从系统层传入,Activity 先接收。
- 通常会把事件传给当前的
DecorView
(根 View)。
ViewGroup.dispatchTouchEvent()
- 尝试调用
onInterceptTouchEvent()
判断是否拦截事件。- 返回 true:表示当前 ViewGroup 要处理,子 View 不再收到事件。
- 返回 false:继续把事件传给子 View。
- 尝试调用
- 子
View.dispatchTouchEvent()
- 如果是 ViewGroup,会重复上面的流程(递归)。
- 如果是普通 View,直接调用
onTouchEvent()
。
onTouchEvent()
- 如果返回
true
,表示事件被消费(消费后不会再向上传递)。 - 如果返回
false
,当前控件不处理,事件会被传回上层 ViewGroup 的onTouchEvent()
。
- 如果返回
2. Activity、ViewGroup、View事件分发机制分析
2.1 Activity事件分发机制
Activity.dispatchTouchEvent(MotionEvent event)
源码(Activity.java
仅关键代码):
java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
onUserInteraction()
:通知用户交互,和事件分发无关。- 调用
getWindow().superDispatchTouchEvent(ev)
:Window
是PhoneWindow
。PhoneWindow
把事件交给了DecorView
(一个 ViewGroup)处理。
- 如果
superDispatchTouchEvent(ev)
返回true
,说明事件被下面消费了。 - 否则调用
Activity.onTouchEvent(ev)
(比如点击空白处)。
2.2 ViewGroup 事件分发机制
DecorView
是 ViewGroup
,所以它遵循 ViewGroup 的事件分发规则。
ViewGroup.dispatchTouchEvent(MotionEvent ev)
源码(ViewGroup.java
仅关键代码):
ViewGroup.dispatchTouchEvent
代码可大致简化为下面这个样子
java
public boolean dispatchTouchEvent(MotionEvent ev) {
// 1. 是否拦截事件
boolean intercepted = onInterceptTouchEvent(ev);
// 2. 如果没有拦截,遍历子 View 分发
if (!intercepted) {
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
if (child.dispatchTouchEvent(ev)) {
return true;
}
}
}
// 3. 子 View 没有消费,自己处理
return super.dispatchTouchEvent(ev);
}
详细代码可以查看ViewGroup.dispatchTouchEvent
中 dispatchTransformedTouchEvent
方法
java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// 省略....
return handled;
}
onInterceptTouchEvent(ev)
:决定是否拦截事件(默认返回false
)。- 如果返回
true
,自己处理,不再传递给子 View。 - 如果没有拦截,会遍历子 View,调用子 View 的
dispatchTouchEvent(ev)
。 - 如果有任何一个子 View 返回了
true
,说明消费了事件,整个流程结束。 - 如果子 View 都没有消费,最后调用自己的
super.dispatchTouchEvent(ev)
,即作为普通 View 处理。
2.3 View 事件分发机制
View
是最终事件的接收者。
View.dispatchTouchEvent(MotionEvent ev)
源码(View.java
仅关键代码):
java
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
// 1. 先判断是否需要触发 OnTouchListener
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 2. 如果 OnTouchListener 没消费,再走 onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
}
- 优先执行
onTouchListener.onTouch()
。- 如果返回
true
,表示事件被消费,不继续往下传递。
- 如果返回
- 否则走
onTouchEvent(event)
。
因为点击事件是在 onTouchEvent
中的 case MotionEvent.ACTION_UP:
中判断调用的,具体查看 View.performClickInternal()
方法。
这里就是,如果你设置了某个 View
的 OnTouchListener
并且在 onTouch
方法中返回 true
,那么这个 View
的 onClick
方法不会执行的原因。
3. 理解 ViewGroup 的递归式事件分发?
核心理解:递归式分发
- 父 ViewGroup 收到事件,先问自己:"要不要拦截?"(
onInterceptTouchEvent
) - 如果 不拦截,就 找出被点击的子 View
- 然后 把事件递给子 View 的
dispatchTouchEvent()
方法 - 子 View 又可以是一个
ViewGroup
(比如 LinearLayout),于是子 View 又重复上面的流程:onInterceptTouchEvent()
- 再分发给自己的子 View。
- 就这样,一层一层递归下去,直到遇到一个普通 View(没有子 View 的 Button、TextView),最后交给
onTouchEvent()
来消费。
打个通俗比喻:
- 一个 ViewGroup 就像一个"村长",负责分发任务。
- 它收到任务(MotionEvent)后,会问:
- 我要自己干?(拦截)
- 还是派给手下某个小村民?(子 View)
- 村民又是个小村长(嵌套 ViewGroup)的话,继续往下派。
- 最后一个真正干活的是普通农民(Button/TextView)。
补充个知识点:
onInterceptTouchEvent() 只在 "ACTION_DOWN" 开始时有意义!!
- 因为一旦一个手指 ACTION_DOWN 被拦截了,后续的 ACTION_MOVE / ACTION_UP 事件都跟着这个处理链走。
- 如果
DOWN
没拦截,后面的MOVE/UP
也不会随便切换到拦截。
这叫做 事件的捕获(capture)机制,Android 保证事件流动的一致性。
4. 最后
Android 事件分发是一层层向下传,遇到拦截或者消费就停;否则事件会向上传递,直到有人消费或者丢弃。
还有一个问题,对于没了解过事件分发机制的同学来说,对于事件分发:由外到内,事件消费:由内到外 的理解,可能有些困惑,包括我自己,其实就可以简单理解为 ViewGroup 一个方法把事件分发机制写完,方法中间就是挨个遍历子 View 挨个问,你要不要这个事件(由外到内),都不要的话,我就要了(又回到了 由内到外 ),带着这个理解,去看源码就很容易理解事件分发机制了。