Android 多点触控详解,在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案,本次带大家了解 Android 多点触控相关的一些知识。
多点触控 ( Multitouch,也称 Multi-touch ),即同时接受屏幕上多个点的人机交互操作,多点触控是从 Android 2.0 开始引入的功能,在 Android 2.2 时对这一部分进行了重新设计。
Android 将所有的事件都封装进了 Motionvent 中。
我们可以通过复写 onTouchEvent 或者设置 OnTouchListener 来获取 View 的事件。
多点触控获取事件类型请使用 getActionMasked() 。
追踪事件流请使用 PointId。
多点触控相关的事件:
一、多点触控相关问题
在引入多点触控之前,事件的类型很少,基本事件类型只有按下(down)、移动(move) 和 抬起(up),即便加上那些特殊的事件类型也只有几种而已,所以我们可以用几个常量来标记这些事件,在使用的时候使用 getAction() 方法来获取具体的事件,之后和这些常量进行对比就行了。
在 Android 2.0 版本的时候,开始引入多点触控技术,由于技术上并不成熟,硬件和驱动也跟不上,多数设备只能支持追踪两三个点而已,因此在设计 API 上采取了一种简单粗暴的方案,添加了几个常量用于多点触控的事件类型的判断。
注意观察上面编 和id的变化,有两个问题,1、B手指的编 变化了。2、A手指和C手指id是相同的(A手指抬起后,C手指按下替代了A手指)。所以这就引出了一个问题:如果存在 ACTION_POINTER_X_MOVE,那么X应该用什么标志呢 会变化,id虽然不会变化,但id会被复用,例如A手指抬起后C手指按下,C手指复用了A手指的id。所以不论使用哪一个都不能保证唯一性。
当然了,解决问题最好的方式就是把问题抛出去,既然从硬件和软件上都不能保证唯一性和不变性,就不做区分了,因此所有的 move 事件都是 ACTION_MOVE, 具体是哪个手指产生的 move 用户可以结合其他事件(按下和抬起)来综合判断。
2.超过4个手指怎么办/p>
2.0 兼容版,在2.2 之前的设计中,其提供的常量最多能判断四个手指的抬起和落下,当超过四个手指时怎么办呢/p>
由于在 2.2 版本之前,由于没有 getActionMasked 方法,我们可以自己自己手动进行计算,例如下面这样 :
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;
}
在上面的例子中有几点比较关键
2.1、action 与 Index 的获得
我们在 MotionEvent详解 中了解过,Android中的事件一般用最后8位来表示事件类型,再往前8位来表示Index。
例如多指触控的按下事件,其事件类型是 0x00000005, 其Index标志位是 0x00000005,随着更多的手指按下,其中变化的部分是 Index 标志位,最后两位是始终不变的,所以我们只要能将这两个分离开就行了。
取得事件类型(action)
// 获取事件类型
int action = event.getAction() & MotionEvent.ACTION_MASK;
这个非常简单,ACTION_MASK=0x000000ff, 与 getAction() 进行按位与操作后保留最后8位内容(十六进制每一个字符转化为二进制是4位)。
例如:
0x00000105 & 0x000000ff = 0x00000005
取得事件索引(index)
// 获取index编
int index = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
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,这个你可以查看官方文档或者源码,甚至你直接写 case 0x00000005 也行,抬起也是同理。
2.3、只考虑兼容 2.2 以上的版本
当然了,如果你不需要兼容 2.0 版本,只需要兼容到 2.2 以上的话就很简单了,像下面这样:
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;
}
3. index 和 pointId 的变化规则
在 2.2 版本以上,我们可以通过 getActionIndex() 轻松获取到事件的索引(Index),但是这个事件索引的变化还是有点意思的,Index 变化有以下几个特点:
从 0 开始,自动增长。
如果之前落下的手指抬起,后面手指的 Index 会随之减小。
Index 变化趋向于第一次落下的数值(落下手指时,前面有空缺会优先填补空缺)。
对 move 事件无效。
下面我们逐条解释一下具体含义。
3.1、从 0 开始,自动增长。
这一条非常简单,也很容易理解,而且在 MotionEvent详解 中讲解 getAction() 与 getActionMasked() 也简单说过。
注意最后两次触发的事件,它的 Index 都是 1,这样也比较容易解释,当原本的第 2 个手指抬起后,屏幕上就只剩下两个手指了,之前的第 3 个手指就变成了第 2 个,于是抬起时触发事件的 Index 为 1,即之前落下的手指抬起,后面手指的 Index 会随之减小。
3.3、Index 变化趋向于第一次落下的数值(落下手指时,前面有空缺会优先填补空缺)。
这个就有点神奇了,通过上一条规则,我们知道,某一个手指的 Index 可能会随着其他手指的抬起而变小,这次我们用 4 个手指测试一下 Index 的变化趋势。
4. Move 相关事件
4.1 actionIndex 与 pointerIndex
在 move 中无法取得 actionIndex 的,我们需要使用 pointerIndex 来获取更多的信息,例如某个手指的坐标:
getX(int pointerIndex)
getY(int pointerIndex)
但是这个 pointerIndex 又是什么呢actionIndex 有区别么/p>
实际上这个 pointerIndex 和 actionIndex 区别并不大,两者的数值是相同的,你可以认为 pointerIndex 是特地为 move 事件准备的 actionIndex。
4.2 pointerIndex 与 pointerId
通常情况下,pointerIndex 和 pointerId 是相同的,但也可能会因为某些手指的抬起而变得不同。
4.3 遍历多点触控
先来一个简单的,遍历出多个手指的 move 事件:
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 的变化。
4.4 在多点触控中追踪单个手指
要实现追踪单个手指还是有些麻烦的,需要同时使用上 actionIndex, pointerId 和 pointerIndex,例如,我们只追踪第2个手指,并画出其位置:
/**
* 绘制出第二个手指第位置
*/
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);
}
}
}
这段代码也非常短,其核心就是通过判断数值为 1 的 pointerId 是否存在,如果存在就在 move 的时候取出其坐标,并绘制出来。
注意在第二个手指按下,第一个手指抬起时,此时原本的第二个手指会被识别为第一个,所以图片会直接跳动到第二个手指位置。
为了不出现这种情况,我们可以判断一下 pointId 并且只获取第一个手指的数据,这样就能避免这种情况发生了,如下。
针对多指触控处理后版本:
/**
* 一个可以拖图片动的 View
*/
public class DragView extends CustomView {
String TAG = “Gcs”;
Bitmap mBitmap; // 图片
RectF mBitmapRectF; // 图片所在区域
Matrix mBitmapMatrix; // 控制图片的 matrix
boolean canDrag = false;
PointF lastPoint = new PointF(0, 0);
public DragView(Context context) {
this(context, null);
}
public DragView(Context context, AttributeSet attrs) {
super(context, attrs);
BitmapFactory.Options options = new BitmapFactory.Options();
options.outWidth = 960/2;
options.outHeight = 800/2;
mBitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.drag_test, options);
mBitmapRectF = new RectF(0,0,mBitmap.getWidth(), mBitmap.getHeight());
mBitmapMatrix = new Matrix();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
// ▼ 判断是否是第一个手指 && 是否包含在图片区域内
if (event.getPointerId(event.getActionIndex()) == 0 && mBitmapRectF.contains((int)event.getX(), (int)event.getY())){
canDrag = true;
lastPoint.set(event.getX(), event.getY());
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
// ▼ 判断是否是第一个手指
if (event.getPointerId(event.getActionIndex()) == 0){
canDrag = false;
}
break;
case MotionEvent.ACTION_MOVE:
// 如果存在第一个手指,且这个手指的落点在图片区域内
if (canDrag) {
// ▼ 注意 getX 和 getY
int index = event.findPointerIndex(0);
// Log.i(TAG, “index=”+index);
mBitmapMatrix.postTranslate(event.getX(index)-lastPoint.x, event.getY(index)-lastPoint.y);
lastPoint.set(event.getX(index), event.getY(index));
mBitmapRectF = new RectF(0,0,mBitmap.getWidth(), mBitmap.getHeight());
mBitmapMatrix.mapRect(mBitmapRectF);
invalidate();
}
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, mBitmapMatrix, mDeafultPaint);
}
}
可以看到,比起上一个版本,只添加了少量代码,就变得更加“智能”了,可以准确识别某一个手指,不会因为手指抬起而认错手指。
当然了,很多麻烦问题都有简单的解决方案,假如说我们真的要实现一个可以用两个或者多个手指缩放的控件,何必要自己算呢,可以尝试一下 Android 自带的解决方案:手势检测(GestureDetector),不仅能自动帮你计算好缩放比例和缩放中心,而且还可以检测出 单击、长按、滑屏 等不同的手势,不过这就不是本篇的事情了,以后有时间会写一下有关手势检测的用法(继续挖坑)。
三、总结
前段时间因为各种事情比较忙,这篇文章也没时间去写,所以就一直拖到了现在,期间收到不少读者催更,实在是抱歉了。今后在会尽量保证稳定更新的,争取尽快把自定义View系列这一个大坑填完。
关于多点触控,个人认为还算一个比较重要的知识点。尤其是随着 Android 的发展,很多炫酷的交互操作可能会需要用户进行拖拽操作。在进行这类操作的时候进行一下手指的判断还是相当重要的。
如何兼容 2.0 版本的多点触控(目前大部分都不需要兼容 2.0 了吧)。
actionIndex、pointIndex 与 pointId 的区别和用法。
如何在多点触控中正确的追踪一个手指。
参考资料
相关资源:触屏精灵V1.5-演示版.rar(触摸屏软件触屏交互)_触摸屏交互设计…
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!