ViewGroup的dispatchTouchEvent()方法

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 的作用

  1. 记录触摸事件的目标 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,而无需重新进行命中测试。

  1. 支持多点触控
    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。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容