一、核心概念
事件分发的本质
Android事件分发采用责任链模式,事件从Activity开始,依次经过ViewGroup和View。
整个机制只有一个入口:dispatchTouchEvent方法。
onInterceptTouchEvent和onTouchEvent都不是独立的事件入口,而是被dispatchTouchEvent内部调用的方法。
关键方法说明
- dispatchTouchEvent(MotionEvent ev):唯一的事件入口,系统只调用这个方法
- onInterceptTouchEvent(MotionEvent ev):拦截判断方法,仅ViewGroup有,被dispatchTouchEvent调用
- onTouchEvent(MotionEvent event):事件处理方法,被dispatchTouchEvent调用
二、默认行为 vs 自定义行为
默认行为(推荐使用)
特点: 不重写dispatchTouchEvent,系统使用ViewGroup的默认实现
// ViewGroup的默认dispatchTouchEvent实现
public boolean dispatchTouchEvent(MotionEvent ev) {
// 1. 调用onInterceptTouchEvent判断拦截
if (onInterceptTouchEvent(ev)) {
return onTouchEvent(ev); // 拦截了,自己处理
}
// 2. 不拦截,分发给子View
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
if (child.dispatchTouchEvent(ev)) {
return true; // 子View处理了
}
}
// 3. 子View都不处理,自己处理
return onTouchEvent(ev);
}
使用方式:
class CustomViewGroup : LinearLayout {
// 不重写dispatchTouchEvent
// 只重写onInterceptTouchEvent控制拦截逻辑
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> return false
MotionEvent.ACTION_MOVE -> {
val deltaY = abs(ev.y - mInitialY)
return deltaY > 10 // 根据移动距离决定是否拦截
}
}
return false
}
}
优点:
- 代码简洁,只需关注拦截逻辑
- 使用系统验证过的默认实现,不容易出错
- 维护成本低
自定义行为(特殊情况使用)
特点: 重写dispatchTouchEvent,需要手动处理所有逻辑
class CustomViewGroup : LinearLayout {
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
// 方式1:调用super保持默认行为
return super.dispatchTouchEvent(event)
// 方式2:完全自定义处理
// 需要手动实现拦截判断、子View分发、事件处理等所有逻辑
}
}
使用场景:
- 需要特殊的事件处理逻辑
- 需要记录或调试事件
- 需要过滤某些事件
- 需要修改默认的事件分发行为
注意事项:
- 如果不调用super.dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent不会被调用
- 需要手动处理所有边界情况
- 容易出错,维护成本高
三、拦截机制详解
拦截的本质
拦截不是独立的事件入口,而是dispatchTouchEvent内部的一个判断步骤。当系统调用ViewGroup.dispatchTouchEvent(ev)时,在方法内部会调用onInterceptTouchEvent(ev)来判断是否拦截。
拦截的时机和策略
ACTION_DOWN时通常不拦截:
case MotionEvent.ACTION_DOWN:
return false; // 让子View有机会处理点击事件
ACTION_MOVE时判断是否拦截:
kotlin
Apply
case MotionEvent.ACTION_MOVE:
val deltaY = abs(ev.y - mInitialY)
if (deltaY > 10) {
return true; // 拦截滑动事件
}
return false;
拦截的影响
拦截前:
ViewGroup.dispatchTouchEvent(ACTION_MOVE)
↓
ViewGroup.onInterceptTouchEvent(ACTION_MOVE) → 返回false
↓
子View.dispatchTouchEvent(ACTION_MOVE)
↓
子View.onTouchEvent(ACTION_MOVE)
拦截后:
ViewGroup.dispatchTouchEvent(ACTION_MOVE)
↓
ViewGroup.onInterceptTouchEvent(ACTION_MOVE) → 返回true
↓
ViewGroup.onTouchEvent(ACTION_MOVE)
// 子View收不到事件
四、实际应用场景
ScrollView的拦截实现
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mInitialY = ev.y
mInitialX = ev.x
return false // 不拦截,让子View处理点击
}
MotionEvent.ACTION_MOVE -> {
val deltaY = abs(ev.y - mInitialY)
val deltaX = abs(ev.x - mInitialX)
// 垂直滑动且垂直移动大于水平移动时拦截
return deltaY > TOUCH_SLOP && deltaY > deltaX
}
}
return false
}
ViewPager的拦截实现
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mInitialX = ev.x
mInitialY = ev.y
return false // 不拦截
}
MotionEvent.ACTION_MOVE -> {
val deltaX = abs(ev.x - mInitialX)
val deltaY = abs(ev.y - mInitialY)
// 水平滑动且水平移动大于垂直移动时拦截
return deltaX > TOUCH_SLOP && deltaX > deltaY
}
}
return false
}
五、重要原则
返回值含义
- true:消费事件,事件不会继续传递
- false:不消费事件,事件继续向下传递
拦截原则
- ACTION_DOWN时通常不拦截:让子View有机会处理点击
- 拦截后要负责到底:必须处理所有后续事件
- 子View会收到ACTION_CANCEL:当父View拦截后
设计原则
- 统一入口:所有事件都通过dispatchTouchEvent
- 内部判断:拦截判断是分发过程的一部分
- 控制流向:在分发过程中决定是否拦截
- 避免重复:拦截后子View就不会收到事件
六、推荐的最佳实践
1. 大多数情况下使用默认行为
class CustomViewGroup : LinearLayout {
// 只重写onInterceptTouchEvent,不重写dispatchTouchEvent
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
// 在这里写拦截逻辑
return super.onInterceptTouchEvent(ev)
}
}
2. 需要特殊处理时才重写dispatchTouchEvent
class CustomViewGroup : LinearLayout {
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
// 自定义逻辑
Log.d(TAG, "自定义处理: ${event.actionMasked}")
// 调用super保持默认行为
val result = super.dispatchTouchEvent(event)
// 自定义逻辑
Log.d(TAG, "处理结果: $result")
return result
}
}
3. 避免的做法
class CustomViewGroup : LinearLayout {
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
// 不调用super,直接返回
// 这样onInterceptTouchEvent和onTouchEvent都不会被调用
return true // 避免这样做
}
}
七、总结
Android事件分发机制的核心是统一入口和内部判断。系统只调用dispatchTouchEvent方法,而onInterceptTouchEvent和onTouchEvent都是在dispatchTouchEvent内部被调用的。
默认行为提供了完整的事件分发实现,你只需要重写onInterceptTouchEvent来控制拦截逻辑,这是大多数情况下的推荐做法。
自定义行为需要你手动处理所有事件分发逻辑,虽然灵活性更高,但容易出错,只在需要特殊处理时才使用。
理解这个机制的关键是认识到拦截不是独立的事件入口,而是分发过程的一个判断步骤,这样设计确保了事件分发的统一性和可控性。