多点触控详解

概述

多点触控 ( Multitouch,也称 Multi-touch ),即同时接受屏幕上多个点的人机交互操作,多点触控是从 Android 2.0 开始引入的功能,在 Android 2.2 时对这一部分进行了重新设计。

相关事件

相关方法

自己自己手动进行计算,判断手指的抬起和落下

兼容2.2以前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
String TAG = "Gcs";

int action = event.getAction() & MotionEvent.ACTION_MASK;
int index = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;

switch (action) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"第1个手指按下");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"最后1个手指抬起");
break;
case MotionEvent.ACTION_POINTER_1_DOWN: // 此时相当于 ACTION_POINTER_DOWN
Log.e(TAG,"第"+(index+1)+"个手指按下");
break;
case MotionEvent.ACTION_POINTER_1_UP: // 此时相当于 ACTION_POINTER_UP
Log.e(TAG,"第"+(index+1)+"个手指抬起");
break;
}

看几个关键点:

1.action 与 Index 的获得

我们在 前面了解过,Android中的事件一般用最后8位来表示事件类型,再往前8位来表示Index。

例如多指触控的按下事件,其事件类型是 0x00000005, 其Index标志位是 0x00000005,随着更多的手指按下,其中变化的部分是 Index 标志位,最后两位是始终不变的,所以我们只要能将这两个分离开就行了。

  • 取得事件类型(action)

    这个非常简单,ACTION_MASK=0x000000ff, 与 getAction() 进行按位与操作后保留最后8位内容(十六进制每一个字符转化为二进制是4位)。

    例如:
    0x00000105 & 0x000000ff = 0x00000005

1
2
// 获取事件类型
int action = event.getAction() & MotionEvent.ACTION_MASK;
  • 取得事件索引(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

1
2
3
// 获取index编号
int index = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;

只考虑兼容 2.2 以上的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
String TAG = "Gcs";

int index = event.getActionIndex();

switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"第1个手指按下");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"最后1个手指抬起");
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.e(TAG,"第"+(index+1)+"个手指按下");
break;
case MotionEvent.ACTION_POINTER_UP:
Log.e(TAG,"第"+(index+1)+"个手指抬起");
break;
}
index 和 pointId 的变化规则

在 2.2 版本以上,我们可以通过 getActionIndex() 轻松获取到事件的索引(Index),但是这个事件索引的变化还是有点意思的,Index 变化有以下几个特点:

  • 1、从 0 开始,自动增长。

    这个前面说过了。

  • 2、如果之前落下的手指抬起,后面手指的 Index 会随之减小。

    注意最后两次触发的事件,它的 Index 都是 1,这样也比较容易解释,当原本的第 2 个手指抬起后,屏幕上就只剩下两个手指了,之前的第 3 个手指就变成了第 2 个,于是抬起时触发事件的 Index 为 1,即之前落下的手指抬起,后面手指的 Index 会随之减小。

  • 3、Index 变化趋向于第一次落下的数值(落下手指时,前面有空缺会优先填补空缺)。

  • 4、对 move 事件无效。

    这个也比较容易理解,我们所取得的 Index 属性实际上是从事件上分离下来的,但是 move 事件始终为 0x00000002,也就是说,在 move 时不论你移动哪个手指,使用 getActionIndex() 获取到的始终是数值 0。

    既然 move 事件无法用事件索引(Index)区别,那么该如何区分 move 是那个手指发出的呢?这就要用到 pointId 了,pointId 和 index 最大的区别就是 pointId 是不变的,始终为第一次落下时生成的数值,不会受到其他手指抬起和落下的影响。

pointId 与 index 的异同

Move相关事件
actionIndex 与 pointerIndex

在 move 中无法取得 actionIndex 的,我们需要使用 pointerIndex 来获取更多的信息,例如某个手指的坐标:

1
2
getX(int pointerIndex)
getY(int pointerIndex)

实际上这个 pointerIndex 和 actionIndex 区别并不大,两者的数值是相同的,你可以认为 pointerIndex 是特地为 move 事件准备的 actionIndex。

pointerIndex 与 pointerId

这两个数值使用以下两个方法相互转换:

通常情况下,pointerIndex 和 pointerId 是相同的,但也可能会因为某些手指的抬起而变得不同。

遍历多点触控

先来一个简单的,遍历出多个手指的 move 事件:

1
2
3
4
5
6
7
8
String TAG = "Gcs";
switch (event.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
for (int i = 0; i < event.getPointerCount(); i++) {
Log.i("TAG", "pointerIndex="+i+", pointerId="+event.getPointerId(i));
// TODO
}
}

通过遍历 pointerCount 获取到所有的 pointerIndex,同时通过 pointerIndex 来获取 pointerId,可以通过不同手指抬起和按下后移动来观察 pointerIndex 和 pointerId 的变化。

在多点触控中追踪单个手指:

要实现追踪单个手指还是有些麻烦的,需要同时使用上 actionIndex, pointerId 和 pointerIndex,例如,我们只追踪第2个手指,并画出其位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
* 绘制出第二个手指第位置
*/
public class MultiTouchTest extends CustomView {
String TAG = "Gcs";

// 用于判断第2个手指是否存在
boolean haveSecondPoint = false;

// 记录第2个手指第位置
PointF point = new PointF(0, 0);

public MultiTouchTest(Context context) {
this(context, null);
}

public MultiTouchTest(Context context, AttributeSet attrs) {
super(context, attrs);

mDeafultPaint.setAntiAlias(true);
mDeafultPaint.setTextAlign(Paint.Align.CENTER);
mDeafultPaint.setTextSize(30);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
int index = event.getActionIndex();

switch (event.getActionMasked()) {
case MotionEvent.ACTION_POINTER_DOWN:
// 判断是否是第2个手指按下
if (event.getPointerId(index) == 1){
haveSecondPoint = true;
point.set(event.getY(), event.getX());
}
break;
case MotionEvent.ACTION_POINTER_UP:
// 判断抬起的手指是否是第2个
if (event.getPointerId(index) == 1){
haveSecondPoint = false;
point.set(0, 0);
}
break;
case MotionEvent.ACTION_MOVE:
if (haveSecondPoint) {
// 通过 pointerId 来获取 pointerIndex
int pointerIndex = event.findPointerIndex(1);
// 通过 pointerIndex 来取出对应的坐标
point.set(event.getX(pointerIndex), event.getY(pointerIndex));
}
break;
}

invalidate(); // 刷新

return true;
}

@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(mViewWidth/2, mViewHeight/2);
canvas.drawText("追踪第2个按下手指的位置", 0, 0, mDeafultPaint);
canvas.restore();

// 如果屏幕上有第2个手指则绘制出来其位置
if (haveSecondPoint) {
canvas.drawCircle(point.x, point.y, 50, mDeafultPaint);
}
}
}
0%