Jimmy Chen

A Programmer

(转载)Input系统: InputReader处理触摸事件

手机一般有两种类型的输入设备。一种是键盘类型的输入设备,通常它包含电源键和音量下键。另一种是触摸类型的输入设备,触摸屏就属于这种类型。

键盘类型的输入设备一般都是产生按键事件,前面已经用几篇文章,分析了按键事件的分发流程。

触摸类型的输入设备一般都是产生触摸事件,本文就开始分析触摸事件的分发流程。

1. InputMapper 处理触摸事件

Input系统: InputReader 处理按键事件 可知,InputReader 从 EventHub 获取到事件后,最终把事件交给 InputMapper 进行处理。

《(转载)Input系统: InputReader处理触摸事件》

对于键盘类型的输入设备,它的按键事件由 KeyboardInputManager 处理。对于触摸类型的输入设备,如果设备支持多点触摸,它的触摸事件由 MultiTouchInputMapper 处理,而如果只支持单点触摸,它的触摸事件由 SingleTouchInputMapper 处理。

通常,手机上的触摸屏都是支持多点触摸的,那么就看看 MultiTouchInputMapper 处理触摸事件的流程

为了方便大家理解这里的处理过程,我展示一段在触摸屏上滑动手指所产生的触摸事件序列

对于每一次的触摸事件,例如手指按下或者移动,驱动会先上报它的信息事件,例如 x, y 坐标事件,再加上一个同步事件(SYN_REPORT)。

那么,MultiTouchInputMapper 处理触摸事件的过程就很好理解了,如下

  1. 使用累加器 MultiTouchMotionAccumulator 收集触摸事件的信息。参考【2. 收集触摸事件信息
  2. 调用父类 TouchInputMapper::process() 处理同步事件。参考 【3. 处理同步事件

2. 收集触摸事件信息

在分析累加器收集触摸事件信息之前,首先得理解多点触摸协议,也就是 A / B 协议。B 协议也叫 slot 协议,下面简单介绍下这个协议。

当第一个手指按下时,会有如下事件序列

事件 ABS_MT_SLOT,表明触摸信息事件,是由哪个槽(slot)进行上报的。一个手指产生的触摸事件,只能由同一个槽进行上报。

事件 ABS_MT_TRACKING_ID ,表示手指ID。手指 ID 才能唯一代表一个手指,槽的 ID 并不能代表一个手指。因为假如一个手指抬起,另外一个手指按下,这两个手指的事件可能由同一个槽进行上报,但是手指 ID 肯定是不一样的。

事件 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 表示触摸点的 x, y 坐标值。

事件 SYN_REPORT 是同步事件,它表示系统需要同步并处理之前的事件。

当第一个手指移动时,会有如下事件

此时没有指定 ABS_MT_SLOT 事件和 ABS_MT_TRACKING_ID 事件,默认使用前面的值,因为此时只有一个手指。

当第二个手指按下时,会有如下事件

很简单,第二个手指的事件,由另外一个槽进行上报。

当两个手指同时移动时,会有如下事件

通过指定槽,就可以清晰看到事件由哪个槽进行上报,从而就可以区分出两个手指产生的事件。

当其中一个手指抬起时,会有如下事件

当一个手指抬起时,ABS_MT_TRACKING_ID 事件的值为 -1,也就是十六进制的 ffffffff。通过槽事件,可以知道是第一个手指抬起了。

如果最后一个手指也抬起了,会有如下事件

通过 ABS_MT_TRACKING_ID 事件可知,手指是抬起了,但是哪个手指抬起了呢?由于抬起的是最后一个手指,因此省略了槽事件。

现在已经了解了 slot 协议,现在让我来看看累加器 MultiTouchMotionAccumulator 是如何收集这个协议上报的数据的

收集 slot 协议上报的数据的过程如下

  1. 首先根据 ABS_MT_SLOT 事件,获取数组索引。如果上报的数据中没有指定 ABS_MT_SLOT 事件,那么默认用最近一次的 ABS_MT_SLOT 事件的值。
  2. 根据索引,从数组 mSlots 获取 Slot 元素,并填充数据。

很简单,就是用 Slot 数组的不同元素,收集不同手指所产生的事件信息。

3. 处理同步事件

根据前面的分析可知,驱动每次上报完触摸事件信息后,都会伴随着一个同步事件。刚才已经收集了触摸事件的信息,现在来看下如何处理同步事件

处理同步事件的过程如下

  1. 调用 syncTouch() 把累加器收集到数据,同步到 mRawStatesPending 最后一个元素中。syncTouch() 由子类实现。参考【3.1 同步数据
  2. 处理同步过来的数据。同步过来的数据,基本上还是元数据,因此需要对它加工,最终要生成高级事件,并分发出去。参考【3.2 处理同步后的数据

3.1 同步数据

累加器收集的数据是由驱动直接上报的元数据,这里把元数据同步到 RawState::rawPointerData,它的类型为 RawPointerData ,结构体定义如下

介绍下 RawPointerData 的几个成员变量,就可以知道同步后的数据有哪些了

  1. uint32_t pointerCount : 保存触摸的手指数量。
  2. BitSet32 touchingIdBits : 保存所有手指的ID。
  3. Pointer pointers[MAX_POINTERS] : 保存所有手指的触摸事件的元数据。
  4. uint32_t idToIndex[MAX_POINTER_ID + 1] : 保存手指 ID 到 index 的映射。这个 index 就是数组 pointers 的索引。

在这里,我要强调几点事

  • 只有手指 ID 才能唯一代表一个手指。
  • index 只能作为数据的索引,来获取手指的触摸事件信息。
  • 如果你知道了手指ID,那么就可以通过 idToIndex 获取索引,然后根据索引获取手指对应的触摸事件信息。

我曾经写了一篇文章 多手指触控,其实也不是很难 ,这篇文章中强调了,在多手指触摸的情况下,只有手指 ID 能唯一代表一个手指,如果想获取某一个手指的触摸事件,那么必须先将 ID 转化为 index,然后使用这个 index 从数组中获取触摸事件的数据。现在,你懂了吗?

3.2 处理同步后的数据

现在数据已经同步到 mRawStatesPending 最后一个元素中,但是这些数据基本上是元数据,是比较晦涩的,接下来看看如何处理这些数据

开始处理元数据之前,首先使用 mCurrentRawState 复制了当前正在处理的数据,后面会使用它进行前后两次的数据对比,生成高级事件,例如 DOWN, MOVE, UP 事件。

然后调用 cookAndDispatch() 对数据进行加工和分发

加工和分发事件的过程如下

  1. 使用 cookPointerData() 进行加工事件。加工什么呢?例如,由于手指是在输入设备上触摸的,因此需要把输入设备的坐标转换为显示屏的坐标,这样窗口就能接收到正确的坐标事件。参考【3.2.1 加工数据
  2. 使用 dispatchTouches() 进行分发事件。底层上报的数据毕竟晦涩难懂,因此需要包装成 DOWN/MOVE/UP 事件进行分发。参考【3.2.2 分发事件

3.2.1 加工数据

加工的元数据保存到了 CookedState::cookedPointerData 中,它的类型为 CookedPointerData ,结构体定义如下

一看就明白了什么意思把,就不过多介绍了。

对于手机来的触摸屏来说,触摸事件的加工,最主要的就是把触摸屏的坐标点转换为显示屏的坐标点,如下

这是一道初中的坐标系转换的数学题目,我就不献丑去细致分析了,主要过程如下

  1. 首先根据坐标轴的缩放比例 mXScalemYScale,计算触摸屏的坐标点在显示屏的坐标系中的x, y轴的缩放值。
  2. 根据显示屏 x, y 轴的偏移量,以及旋转角度,最终计算出显示屏上的坐标点。

3.2.2 分发事件

元数据已经加工完成,现在是时候来分发了

分发事件的过程,其实就是对比前后两次的数据,生成高级事件 AMOTION_EVENT_ACTION_POINTER_DOWN, AMOTION_EVENT_ACTION_MOVE, AMOTION_EVENT_ACTION_POINTER_UP,然后调用 dispatchMotion() 分发这些高级事件。

可以看到,数据最终被包装成 NotifyMotionArgs 分发到下一环 InputClassifier

但是,在这之前,还对 action 做了如下处理

  1. 为 action 添加一个 index。由于 index 是元数据数组的索引,因此 action 也就是绑定了触摸事件的数据。
  2. 如果是第一个手指按下,把 AMOTION_EVENT_ACTION_POINTER_DOWN 转换为 AMOTION_EVENT_ACTION_DOWN
  3. 如果是最后一个手指抬起,把 AMOTION_EVENT_ACTION_POINTER_UP 转换成 AMOTION_EVENT_ACTION_UP

第2点和第3点,在自定义 View 中处理多手指事件时,是不是很熟悉。

结束

闭上眼睛,想想 InputReader 如何处理触摸事件的。其实就是通过 InputMapper 把触摸屏的坐标点转换为显示屏的坐标点,然后对比前后两次的数据,生成高级事件,然后分发给下一环。so easy !

看我文章的人,是不是大部分是上层的人,前面两篇文章正好是上层应用类型的文章,所以得到大量的点赞反馈。但是须知,经济基础才能决定上层建筑,只有掌握了基础,才能以不变应万变。

关于触摸事件,我也会打算写一篇手势导航的文章,也就是我们经常使用的通过手势进行返回,通过手势回到桌面,这一定是大家最想看到的东西。关注我,后面更精彩。

本文转自 https://juejin.cn/post/7199555741631660093,如有侵权,请联系删除。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注