先问拦截再派送, 子不处理父兜底, 一旦消费无后续, 滑动冲突靠逻辑。
1、事件的传递链路
硬件层 → 系统服务(InputManager) → Activity → Window → 根布局(DecorView) → ViewGroup → 子 View
- 硬件层:屏幕的触摸信号会被底层硬件捕获,转换成原始事件数据。
- 系统服务 :Android 系统的
InputManager
会将事件传递给当前处于前台的 Activity。 - Activity:Activity 作为应用的「窗口管理者」,是事件进入应用的第一个入口。
- Window :Activity 内部通过
Window
(具体实现是PhoneWindow
)将事件传递给根布局(DecorView
)。 - 根布局(DecorView) :所有 Activity 的界面最外层都是一个
DecorView
,它承载了标题栏、状态栏和你的布局内容。 - View 树 :事件从
DecorView
开始,沿着 View 树层层分发,直到找到能处理事件的 View。
这四个口诀分别对应 Android 事件分发机制和滑动冲突处理的核心逻辑,以下是详细解释:
2. 先问拦截再派送
含义 :事件分发时,父容器(如 ViewGroup)会优先决定是否拦截事件 ,再决定是否将事件派发给子 View。
机制:
- 父容器通过
onInterceptTouchEvent()
方法判断是否拦截事件。 - 如果返回
true
(拦截),事件会直接交给父容器的onTouchEvent()
处理,不再传递给子 View。 - 如果返回
false
(不拦截),事件会继续向子 View 传递,由子 View 处理。
示例:
java
public class CustomViewGroup extends ViewGroup {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 根据业务逻辑判断是否拦截事件
if (需要拦截) return true;
return false;
}
}
3. 子不处理父兜底
含义 :如果子 View 不消费事件 (onTouchEvent()
返回 false
),事件会回传给父容器处理,形成"责任链"模式。
机制:
- 子 View 的
onTouchEvent()
返回false
时,父容器的onTouchEvent()
会被调用。 - 如果所有子 View 都不处理,最终会由
Activity
的onTouchEvent()
兜底处理。
示例:
java
public class ChildView extends View {
@Override
public boolean onTouchEvent(MotionEvent event) {
// 子 View 不处理事件
return false; // 触发父容器的 onTouchEvent()
}
}
4. 一旦消费无后续
含义 :如果某个 View 消费了事件 (onTouchEvent()
返回 true
),则后续的整个事件序列(如 ACTION_MOVE
、ACTION_UP
)都会直接交给它处理,不再经过事件分发流程 。
关键点:
- 事件消费后,后续事件会跳过
onInterceptTouchEvent()
,直接通过dispatchTouchEvent()
发送给已消费的 View。 - 确保事件处理的连贯性(例如长按拖动操作)。
示例:
java
public class ConsumerView extends View {
@Override
public boolean onTouchEvent(MotionEvent event) {
// 处理事件并消费
return true; // 后续事件直接交给此 View
}
}
5. 滑动冲突靠逻辑
含义 :解决滑动冲突(如嵌套 ScrollView
、ViewPager
)需要依赖业务逻辑定制事件分发规则 。
常见方案:
-
外部拦截法 :父容器通过
onInterceptTouchEvent()
决定是否拦截。javapublic boolean onInterceptTouchEvent(MotionEvent ev) { if (水平滑动) return true; // 父容器拦截横向滑动 return false; // 不拦截纵向滑动 }
-
内部拦截法 :子 View 通过
requestDisallowInterceptTouchEvent()
控制父容器是否拦截。javapublic boolean dispatchTouchEvent(MotionEvent ev) { if (需要父容器不拦截) { getParent().requestDisallowInterceptTouchEvent(true); } return super.dispatchTouchEvent(ev); }
场景示例:
ViewPager
嵌套ListView
:根据滑动方向判断由谁处理滑动。- 地图(
MapView
)嵌套可缩放控件:根据手势类型(拖动 vs 缩放)决定拦截逻辑。
总结
- 事件分发流程 :
Activity -> Window -> ViewGroup -> View
,每个环节通过拦截和消费控制事件流向。 - 滑动冲突本质:通过逻辑判断(方向、速度、业务需求)动态调整事件分发路径。
- 核心设计模式:责任链模式(子不处理父兜底) + 拦截机制(先拦截再派送)。