本文转载自GcsSloop的 安卓自定义View进阶-多点触控详解 的文章

Android 多点触控详解,在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案,本次带大家了解 Android 多点触控相关的一些知识。

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

在本文开始之前,先回顾一下 MotionEvent详解 中提到过的内容:

  • Android 将所有的事件都封装进了 Motionvent 中。
  • 我们可以通过复写 onTouchEvent 或者设置 OnTouchListener 来获取 View 的事件。
  • 多点触控获取事件类型请使用 getActionMasked()
  • 追踪事件流请使用 PointId

多点触控相关的事件:

事件 简介
ACTION_DOWN 第一个 手指 初次接触到屏幕 时触发。
ACTION_MOVE 手指 在屏幕上滑动 时触发,会多次触发。
ACTION_UP 最后一个 手指 离开屏幕 时触发。
ACTION_POINTER_DOWN 有非主要的手指按下(即按下之前已经有手指在屏幕上)。
ACTION_POINTER_UP 有非主要的手指抬起(即抬起之后仍然有手指在屏幕上)。
以下事件类型不推荐使用 ---以下事件在 2.2 版本以上被标记为废弃---
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 个手指抬起,已废弃,不推荐使用。

多点触控相关的方法:

方法 简介
getActionMasked() getAction() 类似,多点触控需要使用这个方法获取事件类型
getActionIndex() 获取该事件是哪个指针(手指)产生的。
getPointerCount() 获取在屏幕上手指的个数。
getPointerId(int pointerIndex) 获取一个指针(手指)的唯一标识符ID,在手指按下和抬起之间ID始终不变。
findPointerIndex(int pointerId) 通过PointerId获取到当前状态下PointIndex,之后通过PointIndex获取其他内容。
getX(int pointerIndex) 获取某一个指针(手指)的X坐标
getY(int pointerIndex) 获取某一个指针(手指)的Y坐标

回顾完毕,开始正文。

一、多点触控相关问题

在引入多点触控之前,事件的类型很少,基本事件类型只有按下(down)、移动(move) 和 抬起(up),即便加上那些特殊的事件类型也只有几种而已,所以我们可以用几个常量来标记这些事件,在使用的时候使用 getAction() 方法来获取具体的事件,之后和这些常量进行对比就行了。

在 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 个手指抬起,已废弃,不推荐使用。

这些事件类型是用来判断非主要手指(第一个按下的称为主要手指)的按下和抬起,使用起来大概是这样子:

switch (event.getAction()) {case MotionEvent.ACTION_DOWN:           break;case MotionEvent.ACTION_UP:             break;case MotionEvent.ACTION_MOVE:           break;case MotionEvent.ACTION_POINTER_1_DOWN: break;case MotionEvent.ACTION_POINTER_2_DOWN: break;case MotionEvent.ACTION_POINTER_3_DOWN: break;case MotionEvent.ACTION_POINTER_1_UP:   break;case MotionEvent.ACTION_POINTER_2_UP:   break;case MotionEvent.ACTION_POINTER_3_UP:   break;
}

看到这里可能会产生以下的一些疑问?

1.为什么没有 ACTION_POINTER_X_MOVE ?

在多指触控中所有的移动事件都是使用 ACTION_MOVE, 并没有追踪某一个手指的 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 事件都是 ACTION_MOVE, 具体是哪个手指产生的 move 用户可以结合其他事件(按下和抬起)来综合判断。

2.超过4个手指怎么办?

2.0 兼容版,在2.2 之前的设计中,其提供的常量最多能判断四个手指的抬起和落下,当超过四个手指时怎么办呢?

由于在 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_DOWNLog.e(TAG,"第"+(index+1)+"个手指按下");break;case MotionEvent.ACTION_POINTER_1_UP:   // 此时相当于 ACTION_POINTER_UPLog.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 变化有以下几个特点:

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)
第3个手指抬起 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 时不论你移动哪个手指,使用 getActionIndex() 获取到的始终是数值 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 来获取更多的信息,例如某个手指的坐标:

getX(int pointerIndex)
getY(int pointerIndex)

但是这个 pointerIndex 又是什么呢?和 actionIndex 有区别么?

实际上这个 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 事件:

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);}@Overridepublic 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 来获取 pointerIndexint pointerIndex = event.findPointerIndex(1);// 通过 pointerIndex 来取出对应的坐标point.set(event.getX(pointerIndex), event.getY(pointerIndex));}break;}invalidate();   // 刷新return true;}@Overrideprotected 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 的时候取出其坐标,并绘制出来。

虽然逻辑简单,但个人感觉写起来还是有些麻烦,如果有更简单的方案欢迎告诉我。

二、如何使用多点触控

多点触控应用还是比较广泛的,至少目前大部分的图片查看都需要用到多点触控技术(用于拖动和缩放图片)。

但是在某些看似不需要多触控的地方也需要对多点触控进行判断,只要是多点触控可能引起错误的地方都应该加上多点触控的判断。例如使用到 move 事件的时候,由于 move 事件可能由多个手指同时触发,所以可能会出现同时被多个手指控制的情况,如果不适当的处理,这个 move 就可能由任何一个手指触发。

举一个简单的例子:

如果我们需要一个可以用单指拖动的图片。假如我们不进行多指触控的判断,像下面这样:

没有针对多指触控处理版本:

/*** 一个可以拖图片动的 View*/
public class DragView1 extends CustomView {String TAG = "Gcs";Bitmap mBitmap;         // 图片RectF mBitmapRectF;     // 图片所在区域Matrix mBitmapMatrix;   // 控制图片的 matrixboolean canDrag = false;PointF lastPoint = new PointF(0, 0);public DragView1(Context context) {this(context, null);}public DragView1(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();}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getActionMasked()) {case MotionEvent.ACTION_DOWN:// 判断按下位置是否包含在图片区域内if (mBitmapRectF.contains((int)event.getX(), (int)event.getY())){canDrag = true;lastPoint.set(event.getX(), event.getY());}break;case MotionEvent.ACTION_UP:canDrag = false;case MotionEvent.ACTION_MOVE:if (canDrag) {// 移动图片mBitmapMatrix.postTranslate(event.getX() - lastPoint.x, event.getY() - lastPoint.y);// 更新上一次点位置lastPoint.set(event.getX(), event.getY());// 更新图片区域mBitmapRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());mBitmapMatrix.mapRect(mBitmapRectF);invalidate();}break;}return true;}@Overrideprotected void onDraw(Canvas canvas) {canvas.drawBitmap(mBitmap, mBitmapMatrix, mDeafultPaint);}
}

这个版本非常简单,当然了,如果正常使用(只使用一个手指)的话也不会出问题,但是当使用多个手指,且有抬起和按下的时候就可能出问题,下面用一个典型的场景演示一下:

注意在第二个手指按下,第一个手指抬起时,此时原本的第二个手指会被识别为第一个,所以图片会直接跳动到第二个手指位置。

为了不出现这种情况,我们可以判断一下 pointId 并且只获取第一个手指的数据,这样就能避免这种情况发生了,如下。

针对多指触控处理后版本:

/*** 一个可以拖图片动的 View*/
public class DragView extends CustomView {String TAG = "Gcs";Bitmap mBitmap;         // 图片RectF mBitmapRectF;     // 图片所在区域Matrix mBitmapMatrix;   // 控制图片的 matrixboolean 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();}@Overridepublic 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 和 getYint 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;}@Overrideprotected void onDraw(Canvas canvas) {canvas.drawBitmap(mBitmap, mBitmapMatrix, mDeafultPaint);}
}

可以看到,比起上一个版本,只添加了少量代码,就变得更加“智能”了,可以准确识别某一个手指,不会因为手指抬起而认错手指。

重点注意最后,第一个手指抬起之后,图片并没有跳跃到第二个手指的位置。

上面的两个对比示例都精简到了极致,其核心依旧是正确的追踪某一个手指,建议大家自己写一遍体会一下。


我感觉很多人看到这里依旧是不明所以的,一些简单的东西还好弄,但是复杂一些,如同时处理多个手指的数值就有些困难了,假如说你之前没有接触过多点触控的处理,此时让你实现用两个手指来缩放图片还是有些困难的。

因为这不仅要追踪两个手指的位置,还要根据位置变化来计算缩放比例和缩放中心,单单这两个非常简单的数学问题就能难倒一大批人。

当然了,很多麻烦问题都有简单的解决方案,假如说我们真的要实现一个可以用两个或者多个手指缩放的控件,何必要自己算呢,可以尝试一下 Android 自带的解决方案:手势检测(GestureDetector、ScaleGestureDecetor),不仅能自动帮你计算好缩放比例和缩放中心,而且还可以检测出 单击、长按、滑屏 等不同的手势,不过这就不是本篇的事情了,以后有时间会写一下有关手势检测的用法(继续挖坑)。

三、总结

前段时间因为各种事情比较忙,这篇文章也没时间去写,所以就一直拖到了现在,期间收到不少读者催更,实在是抱歉了。今后在会尽量保证稳定更新的,争取尽快把自定义View系列这一个大坑填完。 ˊ_>ˋ

关于多点触控,个人认为还算一个比较重要的知识点。尤其是随着 Android 的发展,很多炫酷的交互操作可能会需要用户进行拖拽操作。在进行这类操作的时候进行一下手指的判断还是相当重要的。

本文中需要注意的几个知识点:

  • 如何兼容 2.0 版本的多点触控(目前大部分都不需要兼容 2.0 了吧)。
  • actionIndex、pointIndex 与 pointId 的区别和用法。
  • 如何在多点触控中正确的追踪一个手指。

参考资料

MotionEvent


http://www.taodudu.cc/news/show-4231510.html

相关文章:

  • linux多点触摸屏驱动程序,Linux系统实现支持多点触控操作[视频]
  • STM32模拟USB多点触控屏
  • windows编程之WM_MENUSELECT消息
  • 在Ubuntu-server下安装i3wm并简单配置
  • archlinux+i3wm安装与配置
  • mfc,WM_CTLCOLOR,WM_PAINT
  • C#可以编辑的ListView
  • 在i3wm中修改默认终端
  • WM_CHAR,WM_UNICHAR,WM_IME_CHAR
  • python tkinter.Text 高级用法 -- 设计功能齐全的文本编辑器
  • manjaro i3wm社区版配置记录
  • WM_INITDIALOG与WM_CREATE消息的区别
  • SendMessage函数完全使用手册 (解读WM_)
  • windows编程13-WM_CTLCOLORSTATIC
  • linux 全选 编辑文本_强烈推荐:Linux终端文本编辑器Micro
  • WM_CTLCOLOR消息重载
  • MFC编辑框数据读写
  • 关于WM_COMMAND与WM_NOTIFY
  • VC中的所有WM消息
  • 2017-2018 ACM-ICPC, Asia Daejeon Regional Contest F.Philosopher’s Walk
  • 2017-2018 ACM-ICPC, Asia Daejeon Regional Contest C题
  • 例题 9-8 颜色的长度(Color Length, ACM/ICPC Daejeon 2011, UVa1625)
  • gym103627L XXII Open Cup, GP of Daejeon Curly Racetrack 题解
  • 化工学matlab,化工和石化行业
  • matlab航电系统仿真,航空航天和国防
  • matlab学习增强学习,使用 MATLAB 和 Simulink 进行强化学习
  • Philosopher’s Walk ICPC 2017 Daejeon F dfs 分治
  • 2017-2018 ACM-ICPC, Asia Daejeon Regional Contest:Gym 101667L
  • Regionals 2014 Asia - Daejeon
  • Regionals 2015 Asia - Daejeon acmliveoj7233 - Polynomial

Android多点触控详解相关推荐

  1. 模拟Android多点触控

    Android多点触控 Android多点触控 多点触控实现思路 第一种adb shell input方式 第二种adb shell sendevent方式 多点触控实现思路   经过资料的查询,要在 ...

  2. Android多点触控揭秘

    本文原创,转载请注明:http://blog.csdn.net/cloudzfy1/article/details/6582707 Google 暑期大学生博客分享大赛 - 2011 Android ...

  3. Android多点触控技术

    1 简介 Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouc ...

  4. Android 多点触控消息捕获与处理

    1 简介 Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouc ...

  5. Android 多点触控 MotionEvent详解

    相关API 介绍 MotionEvent.getY() 和 MotionEvent.getRawY() 的区别 getY 表示触摸事件在当前的View内的Y 坐标, getRawY表示触摸事件在整个屏 ...

  6. Android多点触控MultiTouch浅析

    申明: 参考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0226/914.html 下面实现如何通过应用层支持多点触控操作, ...

  7. android 多点触控缩放,Android多点触控(图片的缩放Demo)

    本文主要介绍Android的多点触控,使用了一个图片缩放的实例,来更好的说明其原理.需要实现OnTouchListener接口,重写其中的onTouch方法. 实现效果图: 源代码: 布局文件: ac ...

  8. 关于android多点触控

    最近项目需要一个多点触控缩放的功能.然后上网查了下资料 总结一下: 首先android sdk版本很重要,比如你在AndroidManifest.xml中指定android:minSdkVersion ...

  9. android多点触控

    网上搜索后有很多的多点触控的东西,自己也写个吧,实现的就是在屏幕上画个圆角的矩形随着2点的大小而变换. 下面的东西是在网上找的一个例子然后自己更改的. View Code 1 package com. ...

最新文章

  1. 美多商城之用户注册(用户注册业务实现)
  2. python-子类和派生、继承
  3. 【数据挖掘笔记八】分类:基本概念
  4. 115开jiang监控
  5. 中科院超牛的物理学家,摇滚界无人不知的“李白”,你一定听过他的歌!
  6. 二阶偏微分方程组 龙格库塔法_牛顿法和拟牛顿法——(书中附录B)
  7. cocos2d-x 3.10 显示Box2d 调试视图
  8. java web中的重定向和转发,forward和sendRedriect
  9. vmware虚拟机中常见的3种网络模式
  10. 「微信小程序」有哪些冲击与机会?
  11. apkmirror_how to download your app from apkmirror to get the lastest app version
  12. NAND flash基本概念整理
  13. win10无法找到wlan选项问题解决
  14. HP大中华区总裁孙振耀退休感言(上)
  15. 【投资趋势】中国未来最值得投资的十大行业
  16. 长虹电视android优化要多久,长虹电视运行卡顿内存不足怎么样?最新优化教程...
  17. RFID期末复习 四、五、六、七章节
  18. npm发版报错(404)
  19. 《灵犬雪莉》主题曲《Belle》歌词翻译(非原创)
  20. 辰视智能工业机器人3D视觉引导解决方案曲轴、手板、轴套等汽车零部件上下料案例分享

热门文章

  1. html5 canvas文字标签云3D旋转动画特效
  2. 查找树莓派ip地址的几种方法
  3. SLAM 岗位求职与简历书写
  4. 网卡 promiscuous mode 与 MAC 的一些 filter 功能
  5. Word打字很卡顿 Office打字时反应慢 延迟 Excel输入迟钝 PPT卡死的终极解决办法大全(24种方法)
  6. [论文阅读笔记31]UER: An Open-Source Toolkit for Pre-training Models
  7. [UER#6 C]逃跑
  8. leetcode — 46. 全排列(不含重复数字)
  9. Mac键盘和Windows键盘对应表
  10. 距离模糊与多普勒模糊