原文地址
Android多点触控详解
内容
Android 多点触控详解,在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案,本次带大家了解 Android 多点触控相关的一些知识。
多点触控 ( Multitouch,也称 Multi-touch ),即同时接受屏幕上多个点的人机交互操作,多点触控是从 Android 2.0 开始引入的功能,在 Android 2.2 时对这一部分进行了重新设计。
- Android 将所有的事件都封装进了 中。
- 我们可以通过复写 或者设置 来获取 View 的事件。
- 多点触控获取事件类型请使用 。
- 追踪事件流请使用 。
多点触控相关的事件:
事件 | 简介 |
---|---|
ACTION_DOWN | 第一个 手指 初次接触到屏幕 时触发。 |
ACTION_MOVE | 手指 在屏幕上滑动 时触发,会多次触发。 |
ACTION_UP | 最后一个 手指 离开屏幕 时触发。 |
ACTION_POINTER_DOWN | 有非主要的手指按下(即按下之前已经有手指在屏幕上)。 |
ACTION_POINTER_UP | 有非主要的手指抬起(即抬起之后仍然有手指在屏幕上)。 |
以下事件类型不推荐使用 | ---以下事件在 2.2 版本以上被标记为废弃--- |
第 2 个手指按下,已废弃,不推荐使用。 | |
第 3 个手指按下,已废弃,不推荐使用。 | |
第 4 个手指按下,已废弃,不推荐使用。 | |
第 2 个手指抬起,已废弃,不推荐使用。 | |
第 3 个手指抬起,已废弃,不推荐使用。 | |
第 4 个手指抬起,已废弃,不推荐使用。 |
多点触控相关的方法:
方法 | 简介 |
---|---|
getActionMasked() | 与 类似,多点触控需要使用这个方法获取事件类型。 |
getActionIndex() | 获取该事件是哪个指针(手指)产生的。 |
getPointerCount() | 获取在屏幕上手指的个数。 |
getPointerId(int pointerIndex) | 获取一个指针(手指)的唯一标识符ID,在手指按下和抬起之间ID始终不变。 |
findPointerIndex(int pointerId) | 通过PointerId获取到当前状态下PointIndex,之后通过PointIndex获取其他内容。 |
getX(int pointerIndex) | 获取某一个指针(手指)的X坐标 |
getY(int pointerIndex) | 获取某一个指针(手指)的Y坐标 |
回顾完毕,开始正文。
一、多点触控相关问题
在引入多点触控之前,事件的类型很少,基本事件类型只有按下(down)、移动(move) 和 抬起(up),即便加上那些特殊的事件类型也只有几种而已,所以我们可以用几个常量来标记这些事件,在使用的时候使用 方法来获取具体的事件,之后和这些常量进行对比就行了。
在 Android 2.0 版本的时候,开始引入多点触控技术,由于技术上并不成熟,硬件和驱动也跟不上,多数设备只能支持追踪两三个点而已,因此在设计 API 上采取了一种简单粗暴的方案,添加了几个常量用于多点触控的事件类型的判断。
事件 | 简介 |
---|---|
ACTION_POINTER_1_DOWN | 第 2 个手指按下,已废弃,不推荐使用。 |
ACTION_POINTER_2_DOWN | 第 3 个手指按下,已废弃,不推荐使用。 |
ACTION_POINTER_3_DOWN | 第 4 个手指按下,已废弃,不推荐使用。 |
ACTION_POINTER_1_UP | 第 2 个手指抬起,已废弃,不推荐使用。 |
ACTION_POINTER_2_UP | 第 3 个手指抬起,已废弃,不推荐使用。 |
ACTION_POINTER_3_UP | 第 4 个手指抬起,已废弃,不推荐使用。 |
这些事件类型是用来判断非主要手指(第一个按下的称为主要手指)的按下和抬起,使用起来大概是这样子:
看到这里可能会产生以下的一些疑问/p>
1.为什么没有 ACTION_POINTER_X_MOVE /h3>
在多指触控中所有的移动事件都是使用 , 并没有追踪某一个手指的 move 事件类型,个人猜测主要是因为:很难无歧义的实现单独追踪每一个手指。
要理解这个,首先要明白设备是如何识别多点触控的,设备没有眼睛,不能像我们人一样看到有几个手指(或者触控笔)在屏幕上。
目前大多数 Android 设备都是电容屏,它们感知触摸是利用手指(触控笔)与屏幕接触产生的微小电流变化,之后通过计算这些电流变化来得出具体的触摸位置,在多点触控中,当两个触摸点足够靠近时,设备实际上是无法分清这两个点的。因此当两个触摸点靠近(重合)后再分开,设备很可能就无法正确的追踪两个点了,所以也很难实现无歧义的追踪每一个点。
并且从软件上来说,事件的编 产生和复用也是一个大问题,例如下面的场景:
事件 | 手指数量 | 编 变化 |
---|---|---|
一个手指按下(命名为A) | 1 | A手指的编 为0,id为0 |
一个手指按下(命名为B) | 2 | B手指的编 为1,id为1 |
A手指抬起 | 1 | B手指编 变更为0,id不变为1 |
一个手指按下(命名为C) | 2 | C手指编 为0,id为0,B手指编 为1,id为1 |
注意观察上面编 和id的变化,有两个问题,**1、B手指的编 变化了。2、A手指和C手指id是相同的(A手指抬起后,C手指按下替代了A手指)。**所以这就引出了一个问题:如果存在 ACTION_POINTER_X_MOVE,那么X应该用什么标志呢 会变化,id虽然不会变化,但id会被复用,例如A手指抬起后C手指按下,C手指复用了A手指的id。所以不论使用哪一个都不能保证唯一性。
当然了,解决问题最好的方式就是把问题抛出去,既然从硬件和软件上都不能保证唯一性和不变性,就不做区分了,因此所有的 move 事件都是 , 具体是哪个手指产生的 move 用户可以结合其他事件(按下和抬起)来综合判断。
2.超过4个手指怎么办/h3>
2.0 兼容版,在2.2 之前的设计中,其提供的常量最多能判断四个手指的抬起和落下,当超过四个手指时怎么办呢/p>
由于在 2.2 版本之前,由于没有 方法,我们可以自己自己手动进行计算,例如下面这样 :
在上面的例子中有几点比较关键:
2.1、action 与 Index 的获得
我们在 MotionEvent详解 中了解过,Android中的事件一般用最后8位来表示事件类型,再往前8位来表示Index。
例如多指触控的按下事件,其事件类型是 0x00000005, 其Index标志位是 0x00000005,随着更多的手指按下,其中变化的部分是 Index 标志位,最后两位是始终不变的,所以我们只要能将这两个分离开就行了。
取得事件类型(action)
这个非常简单,ACTION_MASK=0x000000ff, 与 getAction() 进行按位与操作后保留最后8位内容(十六进制每一个字符转化为二进制是4位)。
例如:
0x00000105 & 0x000000ff = 0x00000005
取得事件索引(index)
ACTION_POINTER_INDEX_MASK = 0x0000ff00
ACTION_POINTER_INDEX_SHIFT = 8
首先让 getAction() 与 ACTION_POINTER_INDEX_MASK 按位与之后,只保留 Index 那8位,之后再右移8位,最终就拿到了 Index 的真实数值。
例如:
0x00000105 & 0x0000ff00 = 0x00000100
0x00000100 8 = 0x00000001
2.2、用 ACTION_POINTER_1_DOWN 代替 ACTION_POINTER_DOWN
这是因为在 2.0 版本的时候还没有 ACTION_POINTER_DOWN 的这个常量,但是它们两个点数值是相同的,都是 0x00000005,这个你可以查看官方文档或者源码,甚至你直接写 也行,抬起也是同理。
2.3、只考虑兼容 2.2 以上的版本
当然了,如果你不需要兼容 2.0 版本,只需要兼容到 2.2 以上的话就很简单了,像下面这样:
3. index 和 pointId 的变化规则
在 2.2 版本以上,我们可以通过 getActionIndex() 轻松获取到事件的索引(Index),但是这个事件索引的变化还是有点意思的,Index 变化有以下几个特点:
1、从 0 开始,自动增长。
2、如果之前落下的手指抬起,后面手指的 Index 会随之减小。
3、Index 变化趋向于第一次落下的数值(落下手指时,前面有空缺会优先填补空缺)。
4、对 move 事件无效。
下面我们逐条解释一下具体含义。
3.1、从 0 开始,自动增长。
这一条非常简单,也很容易理解,而且在 MotionEvent详解 中讲解 getAction() 与 getActionMasked() 也简单说过。
手指按下 | 触发事件(数值) |
---|---|
第1个手指按下 | ACTION_DOWN (0x00000000) |
第2个手指按下 | ACTION_POINTER_DOWN (0x00000105) |
第3个手指按下 | ACTION_POINTER_DOWN (0x00000205) |
第4个手指按下 | ACTION_POINTER_DOWN (0x00000305) |
注意加粗的位置,数值随着手指按下而不断变大。
3.2、如果之前落下的手指抬起,后面手指的 Index 会随之减小。
这个也比较容易理解,像下面这样:
手指按下 | 触发事件(数值) |
---|---|
第1个手指按下 | ACTION_DOWN (0x00000000) |
第2个手指按下 | ACTION_POINTER_DOWN (0x00000105) |
第3个手指按下 | ACTION_POINTER_DOWN (0x00000205) |
第2个手指抬起 | ACTION_POINTER_UP (0x00000106) |
第3个手指抬起 | ACTION_POINTER_UP (0x00000106) |
注意最后两次触发的事件,它的 Index 都是 1,这样也比较容易解释,当原本的第 2 个手指抬起后,屏幕上就只剩下两个手指了,之前的第 3 个手指就变成了第 2 个,于是抬起时触发事件的 Index 为 1,即之前落下的手指抬起,后面手指的 Index 会随之减小。
3.3、Index 变化趋向于第一次落下的数值(落下手指时,前面有空缺会优先填补空缺)。
这个就有点神奇了,通过上一条规则,我们知道,某一个手指的 Index 可能会随着其他手指的抬起而变小,这次我们用 4 个手指测试一下 Index 的变化趋势。
手指按下 | 触发事件(数值) |
---|---|
第1个手指按下 | ACTION_DOWN (0x00000000) |
第2个手指按下 | ACTION_POINTER_DOWN (0x00000105) |
第3个手指按下 | ACTION_POINTER_DOWN (0x00000205) |
第2个手指抬起 | ACTION_POINTER_UP (0x00000106) |
|
|
第4个手指按下 | ACTION_POINTER_DOWN (0x00000105) |
第3个手指抬起 | ACTION_POINTER_UP (0x00000206) |
这个要和上一个对比这看,重点观察第 3 个手指所触发事件区别,在上一个示例中,随着第 2 个手指的抬起,第 3 个手指变化为第 2(01) 个,所以抬起时触发的是第 2 根手指的抬起事件(删除线部分)。
但是,如果第 2 个手指抬起后,落在屏幕上另外一个手指会怎样过测试,发现另外落下的手指会替代之前第 2 个手指的位置,系统判定为 2(01),而不是顺延下去变成 3(02),并且原本第3个手指的index变为原来数值(02),但是如果继续落下其他的手指,数值则会顺延。
即手指抬起时的 Index 会趋向于和按下时相同,虽然在手指数量不足时,Index 会变小,但是当手指变多时,Index 会趋向于保持和按下时一样。
PS:由于程序是从0开始计数的,所以 0 就是 1, 1 就是 2 …
3.4、对 move 事件无效。
这个也比较容易理解,我们所取得的 Index 属性实际上是从事件上分离下来的,但是 move 事件始终为 0x00000002,也就是说,在 move 时不论你移动哪个手指,使用 获取到的始终是数值 0。
既然 move 事件无法用事件索引(Index)区别,那么该如何区分 move 是那个手指发出的呢就要用到 pointId 了,pointId 和 index 最大的区别就是 pointId 是不变的,始终为第一次落下时生成的数值,不会受到其他手指抬起和落下的影响。
3.5、pointId 与 index 的异同。
相同点 | 不同点 |
---|---|
1. 从 0 开始,自动增长。 | |
2. 落下手指时优先填补空缺(填补之前抬起手指的编 )。 | 1. Index 会变化,pointId 始终不变。 |
4. Move 相关事件
4.1 actionIndex 与 pointerIndex
在 move 中无法取得 actionIndex 的,我们需要使用 pointerIndex 来获取更多的信息,例如某个手指的坐标:
但是这个 pointerIndex 又是什么呢actionIndex 有区别么/strong>
实际上这个 pointerIndex 和 actionIndex 区别并不大,两者的数值是相同的,你可以认为 pointerIndex 是特地为 move 事件准备的 actionIndex。
4.2 pointerIndex 与 pointerId
类型 | 简介 |
---|---|
pointerIndex | 用于获取具体事件,可能会随着其他手指的抬起和落下而变化 |
pointerId | 用于识别手指,手指按下时产生,手指抬起时回收,期间始终不变 |
这两个数值使用以下两个方法相互转换。
方法 | 简介 |
---|---|
getPointerId(int pointerIndex) | 获取一个指针(手指)的唯一标识符ID,在手指按下和抬起之间ID始终不变。 |
findPointerIndex(int pointerId) | 通过 pointerId 获取到当前状态下 pointIndex,之后通过 pointIndex 获取其他内容。 |
通常情况下,pointerIndex 和 pointerId 是相同的,但也可能会因为某些手指的抬起而变得不同。
4.3 遍历多点触控
先来一个简单的,遍历出多个手指的 move 事件:
通过遍历 pointerCount 获取到所有的 pointerIndex,同时通过 pointerIndex 来获取 pointerId,可以通过不同手指抬起和按下后移动来观察 pointerIndex 和 pointerId 的变化。
4.4 在多点触控中追踪单个手指
要实现追踪单个手指还是有些麻烦的,需要同时使用上 actionIndex, pointerId 和 pointerIndex,例如,我们只追踪第2个手指,并画出其位置:
这段代码也非常短,其核心就是通过判断数值为 1 的 pointerId 是否存在,如果存在就在 move 的时候取出其坐标,并绘制出来。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!