Android 的事件分发机制是 Android 开发中的一个重要概念,它决定了触摸事件如何在视图层次结构中传递和处理。以下是 Android 事件分发机制的详细解析:
一、核心概念
1. 事件类型
- • ACTION_DOWN:手指按下
- • ACTION_MOVE:手指移动
- • ACTION_UP:手指抬起
- • ACTION_CANCEL:事件被取消
2. 三个核心方法
public boolean dispatchTouchEvent(MotionEvent ev) // 事件分发
public boolean onInterceptTouchEvent(MotionEvent ev) // 事件拦截(仅ViewGroup有)
public boolean onTouchEvent(MotionEvent ev) // 事件处理
二、事件分发流程
1. 流程图
Activity → PhoneWindow → DecorView → ViewGroup → View
↓ ↓ ↓ ↓ ↓
dispatchTouchEvent() → dispatchTouchEvent() → onTouchEvent()
2. 详细流程
第一阶段:Activity 分发
// Activity.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (getWindow().superDispatchTouchEvent(ev)) {
return true; // 事件被消费
}
return onTouchEvent(ev); // 事件没有被消费
}
第二阶段:ViewGroup 分发
// ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
// 1. 先判断是否拦截
boolean intercepted = onInterceptTouchEvent(ev);
// 2. 如果不拦截,分发给子View
if (!intercepted) {
for (View child : children) {
if (child.dispatchTouchEvent(ev)) {
return true; // 子View消费了事件
}
}
}
// 3. 如果拦截或子View没有消费,自己处理
return super.dispatchTouchEvent(ev);
}
三、关键规则
1. 事件序列一致性
- • 一个事件序列(DOWN → MOVE... → UP)必须由同一个 View 处理
- • 如果某个 View 消费了 ACTION_DOWN,后续事件都会传递给它
2. 拦截机制
- • onInterceptTouchEvent() 只在 ViewGroup 中存在
- • 默认返回 false(不拦截)
- • 可以在需要时拦截事件,比如滑动冲突时
3. 优先级顺序
OnTouchListener > onTouchEvent > OnClickListener
// 执行顺序
1. setOnTouchListener() → onTouch()
2. onTouchEvent()
3. setOnClickListener() → onClick() // 在onTouchEvent()中调用
四、源码解析要点
1. View.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {
// 1. 先检查 OnTouchListener
if (mOnTouchListener != null &&
mOnTouchListener.onTouch(this, event)) {
return true; // Listener消费了事件
}
// 2. 调用 onTouchEvent
return onTouchEvent(event);
}
2. View.onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
// 处理点击状态、长按等
// 最后会检查是否可点击
if (isClickable() || isLongClickable()) {
switch (action) {
case MotionEvent.ACTION_UP:
performClick(); // 触发onClick
break;
}
return true; // 消费事件
}
return false; // 不消费事件
}
五、滑动冲突解决方案
1. 外部拦截法(推荐)
在父容器的 onInterceptTouchEvent() 中处理:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false; // DOWN时不拦截
break;
case MotionEvent.ACTION_MOVE:
if (需要拦截) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false; // UP时不拦截
break;
}
return intercepted;
}
2. 内部拦截法
在子 View 的 dispatchTouchEvent() 中处理:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true); // 请求父容器不拦截
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要处理) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
return super.dispatchTouchEvent(event);
}
六、实际案例分析
案例1:RecyclerView 嵌套滑动
// 自定义LayoutManager或使用NestedScroll机制
recyclerView.setNestedScrollingEnabled(true);
案例2:ViewPager + 横向滑动冲突
// 自定义ViewPager解决
public class CustomViewPager extends ViewPager {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 根据业务逻辑决定是否拦截
if (水平滑动角度 > 阈值) {
return super.onInterceptTouchEvent(ev);
}
return false;
}
}
七、调试技巧
1. 事件分发日志
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("Event", "dispatchTouchEvent: " + MotionEvent.actionToString(ev.getAction()));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("Event", "onTouchEvent: " + MotionEvent.actionToString(event.getAction()));
return super.onTouchEvent(event);
}
2. 使用工具类
// 打印事件坐标
Log.d("Event", "x: " + event.getX() + ", y: " + event.getY());
八、最佳实践
-
- 尽量使用外部拦截法,逻辑更清晰
-
- 避免过度拦截,保证子 View 的正常交互
-
- 合理使用 requestDisallowInterceptTouchEvent()
-
- 考虑 NestedScrolling 机制(Android 5.0+)
-
- 测试不同 Android 版本(某些版本有差异)
九、记忆口诀
事件分发三方法,dispatch/intercept/onTouch
Activity到ViewGroup,再到子View层层传
拦截只在父容器,DOWN不拦是关键
事件序列一致性,谁接DOWN谁负责
滑动冲突两大类,外部拦截最常用