Android 的事件分发机制围绕 三个核心方法 展开:
dispatchTouchEvent:分发事件onInterceptTouchEvent:拦截事件(仅 ViewGroup 有)onTouchEvent:处理事件
它们的关系可以用一个 "责任链 + 决策树" 来描述:事件从 Activity 开始,逐级向下传递,每一层都有机会拦截、消费或继续传递。
1. 三个方法的核心职责
| 方法 | 所属 | 作用 | 默认行为 |
|---|---|---|---|
dispatchTouchEvent |
View / ViewGroup | 决定事件是自己处理 还是传给子 View | 调用 onInterceptTouchEvent(若为 ViewGroup)或 onTouchEvent(若为 View) |
onInterceptTouchEvent |
仅 ViewGroup | 在事件传递给子 View 之前,决定是否拦截 (截断给子 View,转交自己的 onTouchEvent) |
默认返回 false(不拦截);return true 表示拦截 |
onTouchEvent |
View / ViewGroup | 实际处理事件(消费或忽略) | 默认返回 true 如果 View 可点击(clickable / longClickable 等),否则返回 false |
2. 完整的事件分发流程(以 ACTION_DOWN 为例)
text
Activity.dispatchTouchEvent()
└─> ViewGroup.dispatchTouchEvent()
├─> onInterceptTouchEvent()
│ ├─ false → 遍历子 View,调用子 View 的 dispatchTouchEvent()
│ └─ true → 不再分发子 View,调用自身的 onTouchEvent()
└─> ...(子 View 重复上述过程)
关键规则
- 从顶层到底层 :
Activity → ViewGroup → View。 - 拦截发生在分发子 View 之前 :
onInterceptTouchEvent返回true时,本次及后续事件序列都将交给当前 ViewGroup 的onTouchEvent处理,不再询问子 View。 - 事件序列由 ACTION_DOWN 决定 :
- 谁在
DOWN事件中返回true消费了事件,后续的MOVE、UP就直接发送给谁,不重新经历拦截/分发。 - 如果没有任何
onTouchEvent返回true,事件最终会回到Activity.onTouchEvent并被忽略。
- 谁在
onTouchEvent的返回值 :true:消费事件,后续事件仍会传给该 View。false:不消费,事件会回溯给父 View 的onTouchEvent处理。
3. 三者的协作示例(LinearLayout 内嵌 Button)
java
// 点击 Button 时
Button.dispatchTouchEvent() // Button 没有子 View,会直接调用 onTouchEvent
└─ Button.onTouchEvent() // 默认返回 true(因为 button 是可点击的),事件被消费
// 父 ViewGroup(LinearLayout)不拦截,因此 Button 收到事件
LinearLayout.onInterceptTouchEvent() → false
若你想让 LinearLayout 优先处理滑动事件(例如可拖动的容器),可以重写 onInterceptTouchEvent 在滑动时返回 true:
java
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (isDragging(ev)) {
return true; // 拦截,后续事件都给自己处理,子 View 收不到
}
return false;
}
4. 一张图总结调用链
plaintext
dispatchTouchEvent() → 是 ViewGroup 吗?
├─ 是 → onInterceptTouchEvent()
│ ├─ true → onTouchEvent()
│ └─ false → 分发给子 View.dispatchTouchEvent()
└─ 否 → onTouchEvent()
- 返回值 true:事件被消费,不再向上回溯。
- 返回值 false :事件向上传递给父 View 的
onTouchEvent。
5. 常见面试追问与要点
-
onTouchListener.onTouch()与onTouchEvent的关系如果 View 设置了
OnTouchListener,onTouch()会优先于onTouchEvent执行。若onTouch()返回true,onTouchEvent就不会被调用。 -
requestDisallowInterceptTouchEvent(true)子 View 可要求父 View 不拦截事件(常用于嵌套滑动冲突,如 ScrollView 内有可滑动的子控件)。
-
为什么自定义 ViewGroup 时常需要重写
onInterceptTouchEvent?默认实现简单(返回
false),无法处理滑动冲突;你需要根据业务逻辑判断何时拦截。
如果想深入某个场景(如处理横向/纵向滑动冲突、事件如何从 Native 层传递过来),我可以进一步解释。