Android---touch 事件分发

Android touch 事件分发,是 android 工程师必备技能之一。关于事件分发主要有几个方向可以展开深入分析:

\bullet touch 事件是如何从驱动层传递给 Framework 层的 InputManagerService;

\bullet WMS 是如何通过 ViewRoolmple 将事件传递到目标窗口;

\bullet touch 事件到达 DecorView 后,是如何一步步传递到内部子 View 中的。

与上层软件开发息息相关的就是最后一条。

主义:本文是基于 Android-28 的源码上进行分析。

ViewGroup

ViewGroup 是一组 View 的组合,在其内部可能包含多个子 View。当手指触摸屏幕时,手指所在的区域既能在 ViewGroup 显示范围内,也可能在其内部 View 控件上。因此,它内部事件分发的重心是处理当前 Group 和子 View 之间的关系:

1. 当前 Group 是否需要拦截 touch 事件;

2. 是否需要将 touch 事件继续分发给子 View;

3. 如何将 touch 事件分发给子 view。

View

View 是一个单独的控件,它的事件分发重点在于当前 View 如何去处理 touch 事件,并根据相应的手势逻辑进行一些列的效果展示(比如滑动、放大、点击和长按等)

1. 是否存在 TouchListener;

2. 是否自己接收处理 touch 事件(主要逻辑在 onTouchEvent 方法中)。

事件分发核心 dispatchTouchEvent

整个 View 之间的事件分发,实质上是一个大的递归函数,这个递归函数就是 dispatchTouchEvent方法。在这个递归的过程中会适时调用 onInterceptTouchEvent 来拦截事件,或者调用 onTouchEvent 方法来处理事件。

从宏观角度纵览一下 diapatchTouchEvent 方法源码,主要分为3个步骤

步骤1:判断当前 ViewGroup 是否需要拦截事件。如果拦截,此次 touch 事件不会再传递给子 view。

步骤2:没有拦截,则将事件分发给子 view,继续处理。如果子 view 将此次事件捕获,则将 mFirstTouchTarget 复制给捕获 touch 事件的 view。

步骤3:根据 mFirstTouchTarget 重新分发事件。

接下来,详细的看一下每个步骤

步骤1的具体代码如下:

图中,红框部分标出了是否需要拦截的判断条件。如果事件为 DOWN 事件,则调用 onInterceptTouchEvent 来进行拦截。或者 mFirstTouchTarget != null,代表已经有了子 view 捕获了这个事件。子 view 的 dispatchTouchEvent 返回 true,就代表捕获了 touch 事件。

如果步骤1中并没有对当前事件拦截,则进入步骤2

步骤2的具体代码如下:

图中 1 处表明事件主动分发的前提是事件为 DOWN 事件;图中 2 处遍历所有子 View;图中 3 处判断事件坐标是否在子 View 坐标范围内,并且子 View 并没有处在动画状态;图中 4 处调用 dispatcTransformedTouchEvent 方法将事件分发给子 View,如果子 View 捕获事件成功,则将 mFirstTouchTarget 复制给 View。

步骤3的具体代码如下:

图中 1 处,如果此时 mFirstTouchTarget == null,说明在上述的事件分发中,并没有子 View 对事件进行捕获操作。这种情况下直接调用 dispatchTransformedTouchEvent() 方法,并传入 child 为 null,最终会调用 super.dispatchTouchEvent 方法。实际上最终会调用自己的 onTouchEnent 方法进行处理 touch 事件。即,如果没有子 View 捕获处理 touch 事件,ViewGroup 会通过自身的 onTouchEvent 方法进行处理。图中 2 处,mFirstTouchTarget != null,说明上面步骤2中有子 view 对 touch 事件进行了捕获,则将当前以及后续的事件之间交给 mFirstTouchTarget 指向的 view 直接处理。

为什么 Down 事件特殊

1)所有 Touch 事件都是从 Down 事件开始的。这时 DOWN 事件比较特殊的原因之一;

2)DOWN 事件的处理结果会直接影响后续 MOVE、UP 事件的逻辑。

在步骤 2 中,只有 DOWN 事件会传递给子 View 进行捕获判断。一但子 View 捕获成功,后续的 MOVE 和 UP 事件是通过遍历 mFirstTouchTarget 链表,查找之前接受 ACTION_DOWN 的子 View,并将触摸事件分配给这些子 View。后续的 MOVE、UP 等事件的分发交给谁,取决于它们的起始事件 Down 是由谁捕获的

mFirstTouchTarget 有什么作用

mFirstTouchTarget 部分源码如下

可以看出,mFirstTouchTarget 是一个 TouchTarget 链表结构。而这个 TouchTarget 的作用就是用来记录捕获 Down 事件的 View。具体保存在上图中的 child 变量中。

为什么是链表类型的结构呢?

因为 android 设备是支持多指操作的,每个手指的 Down 事件都可以当作一个 TouchTarget 保存起来,在步骤3中判断,如果 mFirstTouchTarget != null,则再次将事件分发给相应的 TouchTarget。

容易被遗漏的 CANCEL 事件

在上面的步骤3中,继续向子 View 分发事件的代码,有一段比较有趣的逻辑,如下

 上图红框中表明已经有了子 View 捕获了 touch 事件,但是蓝色框中的 intercepted 变量又是 true。这种情况下,事件主导权重新回到父视图,ViewGroup 中。并在传递给子 View 的分发事件中,传递了一个 cancelChild == true

再看一下 dispatchTransformedTouchEvent 的部分源码,如下

因为之前传入的参数 cancel 为 true,并且 child != null。最终这个事件会被包装为一个 ACTION_CANCEL 事件传递给 child。

什么情况下会触发这段逻辑呢?

当父视图的 onInterceptTouchEvent 先返回 false,

然后在子 View 的 dispatchTouchEvent 中返回 true(表示子 View 捕获事件)。

关键步骤就是在接下来的 MOVE 过程中

父视图的 onInterceptTouchEvent 又返回 true,intercepted 被重新置为 true,

此时上述逻辑就会触发,子控件就会收到 ACTION_CANCEL 的 touch 事件。

总结

重点分析了 dispatchTouchEvent 事件的流程机制:

\bullet 判断是否需要拦截 --> 主要是根据 onInterceptTouchEvent 方法的返回值来决定是否拦截。

\bullet 在 DOWN 事件中将 touch 事件分发给子 View --> 这一过程如果有子 View 捕获消费了 touch 事件,会对 mFirstTouchTarget 进行赋值;

\bullet DOWN、MOVE、UP事件都会根据 mFirstTouchTarget 是否为 null 决定是自己处理 touch 事件,还是再次分发给子 View。

介绍了整个事件分发中的几个特殊的点:

\bullet DOWN 事件的特殊之处:事件的起点;决定后续事件由谁来消费处理;

\bullet mFirstTouchTarget 的作用:记录捕获消费 touch 事件的 View,是一个链表结构;

\bullet CANCEL 事件的触发场景:当父视图先不拦截,然后在 MOVE 事件中重新拦截,此时子 View 会接收到一个 CANCEL 事件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别偷我的猪_09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值