在Android中,Touch事件的分发在WindowManagerService(借助 InputManagerService)负责采集和分发,在由ViewRootImpl(内部有一个mView变量指向View树的根),负责控制View树的UI绘制和事件消息分发。
当输入设备可用时,比如触屏,Linux内核在/dev/input/中创建对应的设备节点。
IMS(inputManagerService),所所做的工作就是监听/dev/input下的所有的设备节点,当设备节点的数据时会将数据进行加工处理并找到合适的Window(WMS寻找),将输入事件派发给他。
事件采集
- 设备触摸
- 触摸电信号
- 电容屏
- 传感器
- 电路板
- 驱动
- Linux
- IMS(
inputManagerService)
当输入设备可用时,比如触屏,Lunux内核会在/dev/input中创建对应的设备节点,输入事件所产生的原始信息会被Linux内核输入子系统采集,原始信息由Kernel Space的驱动层一直传递到User Space的接设备节点。
事件中转
- WMS(
WindowManagerService)
WMS的职责之一就是输入系统的中转站,WMS(WindowManagerService)作为WIndow的管理者,会配合IMS将输入事件交给合适的Window来处理。
事件分发(分发)
- ViewRootimpl
ViewRoot中 caiquWindowInputEventReceiver进行具体的事件处理
1:事件分发的基本概念
- 触摸事件:Android的触摸事件主要封装在
MotionEvent类中 - 事件类型:包括
ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL等.. - 分发流程:事件从Activity --> ViewGroup --> View的传递过程
2:三个核心方法
dispatchTouchEvent(MotionEvent ev)
作用:负责事件的分发,决定事件是否继续传递
-
返回值:true表示消费了事件,false表示不处理此事件
- true/拦截:调用自身的
onTounchEvent - false/不拦截:将事件传递给子View
- true/拦截:调用自身的
执行逻辑:通常先调用
onInterceptTouchEvent判断是否拦截
onInterceptTouchEvent(MotionEvent ev)
特性:仅ViewGroup拥有此方法
(View没有)作用:ViewGroup特有方法,用于拦截事件
-
返回值:true表示拦截事件,false表示不拦截(默认)
- true/拦截:不再向子View传递
- false/不拦截:默认值
注意:View没有此方法,无法拦截事件
onTouchEvent(MotionEvent event)
- 作用:处理点击事件的具体逻辑
- 返回值:true表示消费事件,false表示不处理事件
- 执行时机:当
dispatchTouchEvent传递到当前View时被调用 - 特殊情况:如果没有子View消费事件,最终会调用此方法
//事件分发机制流程
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) { // 判断是否拦截
// 拦截后调用自己的onTouchEvent
consume = onTouchEvent(ev);
} else {
// 不拦截则分发给子View
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
Activity的事件分发
- 入口:事件首先传递给Activity的dispatchTouchEvent
- 传递:Activity将事件传递给Window,再传递给DecorView
- 最终:事件到达根ViewGroup开始向下分发
ViewGroup的事件分发特点
- 遍历子View:按Z轴顺序遍历子View检查能否接受点击
- 坐标转换:将事件坐标转换为子View的相对坐标
- 优先处理:如果有子View设置了TouchListener,会优先处理
关键差异对比
| 函数 | ViewGroup | View |
|---|---|---|
| dispatchTouchEvent | 决定是否分发给子View | 直接处理事件 |
| onInterceptTouchEvent | 存在,可拦截事件 | 不存在 |
| onTouchEvent | 处理未被分发的事件 | 处理自身事件 |
事件分发流程
- Activity → Window → DecorView(ViewGroup)
- ViewGroup 调用 dispatchTouchEvent
- ViewGroup 调用 onInterceptTouchEvent 判断是否拦截
- 如不拦截,则传递给相应子View
- 子View调用自身的 dispatchTouchEvent 和 onTouchEvent
- 如拦截,则调用自身的 onTouchEvent
事件的内部拦截法和外部拦截法
一:外部拦截法(推荐)
基本原理:
- 在父容器(ViewGroup)的
onInterceptTouchEvent方法中进行拦截控制 - 通过判断滑动方向或条件来决定是否拦截事件
// 父容器实现
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN) {
// DOWN事件不能拦截,否则后续事件都无法接收到
return false;
}
// 根据滑动方向判断是否拦截
if (needIntercept()) {
return true; // 拦截事件,父容器处理
}
return false; // 不拦截,子View处理
}
private boolean needIntercept() {
// 根据滑动距离、方向等条件判断
return Math.abs(deltaX) > Math.abs(deltaY);
}
特点:
- DOWN事件不能拦截:必须返回false,保证父容器能接收到后续事件
- 控制简单:只需在父容器中处理拦截逻辑
- 符合默认机制:与Android事件分发机制一致
二:内部拦截法
基本原理:
- 子View通过
requestDisallowInterceptTouchEvent方法控制父容器是否拦截 - 子View主动告知父容器不要拦截事件
// 子View实现
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 请求父容器不要拦截
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (isScrollToTop()) {
// 如果滑动到顶部,允许父容器拦截
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.dispatchTouchEvent(ev);
}
// 父容器默认不拦截除了DOWN以外的所有事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return true; // DOWN事件必须拦截
}
return false; // 其他事件默认不拦截
}
特点:
- 需要协调配合:子View和父容器都需要参与处理
- 灵活性高:子View可以更精确地控制事件处理
- 复杂度较高:需要在多个地方进行处理
注意事项
- DOWN事件处理:无论哪种方法,ACTION_DOWN 事件都不能被拦截(外部拦截法)或需要特殊处理(内部拦截法)
- 事件完整性:同一事件序列(DOWN-MOVE-UP)应由同一个View处理
- 性能考虑:避免在拦截判断中进行耗时操作