dispatchTouchEvent() 是 Android 中事件分发的核心方法之一,每个 View 和 ViewGroup 都有这个方法,用来处理触摸事件。我们以 ViewGroup.dispatchTouchEvent() 为例,简要分析它主要做了哪些事情:
一、方法入口:接受事件
dispatchTouchEvent(MotionEvent ev) 会在事件进入 View 树时被调用。系统将触摸事件由根 View(通常是 DecorView)逐级往下传递。这里的逐级往下指的是布局中View树的层级。
二、事件处理逻辑
1. 是否拦截事件
调用 onInterceptTouchEvent(ev) 判断是否拦截事件:
如果返回 true:事件不会传给子 View,自己处理;
如果返回 false:继续尝试分发给子 View。
2. ACTION_DOWN 时查找目标子 View
如果是 ACTION_DOWN 且未被拦截,则:
遍历子 View,从上到下、从前到后;
进行命中测试(hit test);
调用子 View 的 dispatchTouchEvent();
如果子 View 消费事件,记录为 TouchTarget(后续事件也发送给它);
这里遍历子 View,为什么要从上到下,从前到后呢?
为什么是反向遍历?
Android 中子 View 的绘制和层级顺序是由添加顺序决定的:
后添加的子 View 绘制在上面;
触摸事件也应该优先传递给上层的 View;
所以要反向遍历子 View 列表。也就是说以用户角度来看,同级View后添加的view更接近用户,那么理所当然应该先得到触摸事件。
3. 后续事件(MOVE、UP)分发给已记录的 TouchTarget
对于非 DOWN 的事件(MOVE、UP、CANCEL):
遍历已有的 TouchTarget 链表;
将事件派发给链表中记录的子 View。
4. 自己处理事件(ViewGroup 本身)
如果没有子 View 消费事件;
或者自己拦截了事件;
则调用自身的 onTouchEvent(ev) 处理。
三、清理工作
如果是 ACTION_UP 或 ACTION_CANCEL,需要清除 TouchTarget 列表,释放引用。
总结:dispatchTouchEvent() 做了什么?
1.判断是否拦截事件(调用 onInterceptTouchEvent)
2.如果不拦截,则尝试将事件分发给子 View
3.记录响应事件的子 View(用 TouchTarget)
4.后续事件继续发给该子 View
5.没有子 View 处理时,自己处理(调用 onTouchEvent)
6.必要时清理状态(如清除 TouchTarget)
其实单看整个分发过程并不是很复杂,其中比较复杂的是TouchTarget也就是查找目标子 View的过程。
在 Android 的事件分发机制中,ViewGroup 负责将触摸事件分发(dispatch)给其子 View。而 TouchTarget 是在这个过程中起关键作用的一个内部类,主要用于记录当前处理事件的子 View。
一、什么是 TouchTarget?
TouchTarget 是 ViewGroup 的一个内部类,其作用是记录当前接收触摸事件的子 View。
它是一个链表结构,用于支持多点触控时记录多个目标 View。
每个 TouchTarget 对象中保存一个具体的子 View。
二、TouchTarget 的作用
- 记录触摸事件的目标 View
当 ViewGroup 在 dispatchTouchEvent() 中遍历子 View 并找到可以接收事件的子 View 时,会通过创建 TouchTarget 来记录这个子 View。
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...//省略
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
这样后续的事件(如 ACTION_MOVE, ACTION_UP)就可以直接发送给这个子 View,而无需重新进行命中测试。
- 支持多点触控
TouchTarget 是一个链表结构,每个节点保存一个子 View 和对应的 pointerId bits:
多点触控时,不同的 pointer 可以对应不同的子 View。
每次有新的 pointer down,ViewGroup 都会判断是否有子 View 接收,并创建相应的 TouchTarget 节点。
三、触摸事件流简要流程(相关于 TouchTarget)
1.ViewGroup.dispatchTouchEvent() 被调用;
2.如果是 ACTION_DOWN:
遍历子 View,寻找命中的 View;
找到后调用子 View 的 dispatchTouchEvent();
如果子 View 消费事件,创建 TouchTarget 并添加到链表中;
3.后续事件(如 MOVE, UP):
遍历 TouchTarget 链表,将事件分发给对应的子 View。