今天晚上刚学习了一个多点触控的小程序,后面想对其做一个定制。在写的时候遇到很多问题,于是乎就查了一下API文档,又到网上查了一下高手的文章,最后自己又实践了一下。终于把多点触控事件监听的大概原理给弄清楚了。下面就写一下我个人对多点触控原理的理解:
一、MotionEvent事件的分类
触控分为两类:单点触控和多点触控。
1、如何获取触控事件信息?
安卓中使用32位(int)来存储触控事件的动作信息和触控索引。高16位暂时不用,后16位中高8位存储触控信息,低8位存储动作信息。
在安卓当中,使用ACTION_MASK、ACTION_MASK_SHIFT、ACTION_POINTER_INDEX_MASK、ACTION_POINTER_INDEX_SHIFT这四个属性来获得触控事件信息。
属性说明:
ACTION_MASK = 8(0x00ff) :动作信息掩码,用于截取目标动作信息;
ACTION_MASK_SHIFT = 8(0x00ff) : 截取动作信息时所需移位个数
ACTION_POINTER_INDEX_MASK =65280(0xff00) :高8位的位置信息掩码,用于截取索引信息
ACTION_POINTER_INDEX_SHIFT= 8(0x00ff) :截取触控索引时所需的移位个数
如果我们想要获取当前触控的全部信息,则使用
getAction();返回的是触控的所有信息
如果我们仅仅需要获取当前触控的动作信息,则使用
getActionMasked();返回当前触控的动作信息,即低8位的信息
如果我们想要获取的是当前触控的索引(比如:当你要监听是哪一个手指离开屏幕)
getActionIndex();可以达到效果,返回当前触控动作的索引
2、如何区别动作类型?
在一般的单点触控事件当中,我们只需要使用getAction()得到动作类型,再用ACTION_UP、ACTION_DOWN区分就可以达到目的。但是在多点触控的时候,则需要我
们使用getActionMasked()获得动作信息,然后再区分动作是属于那一类。
动作类型:
ACTION_DOWN : 第一个手指按下
ACTION_UP : 最后一个手指离开
ACTION_POINTER_DOWN :非第一个手指按下
ACTION_POINTER_UP : 非最后一个手指离开
ACTION_POINTER_1_DOWN : 这是之前SDK版本所使用的,表示第一个手指按下,它的数值跟ACTION_DOWN是相等的
ACTION_POINTER_2_DOWN:第二根手指按下
ACTION_POINTER_3_DOWN:第三根手指按下
ACTION_POINTER_x_DOWN:第x根手指离开
原理:
取mAction(触控信息)低8位,屏蔽高8位;
即: actionType = (ACTION_MASK&mAction);
例如:mAction= 0x0105H (表示索引为1动作为:ACTION_DOWN,第一个手指按下)
则:actionType= (0x00ff&0x0105) = 5 = ACTION_DOWN;
3、如何知道是哪个手指离开?
如果在单点触控的情况下,监听离开直接使用ACTION_UP即可。但如果在多点触控的情况下,就需要我们定位是哪一个手指离开。这个使用就需要使用到getActionIndex()来获取触控动作的索引。从而得知是哪一个点离开了或者哪一个点按下了。
其实getActionIndex()的实现原理很简单:
取mAction(触控动作)高8位信息,然后再右移8位得到索引。
即: Index = (ACTION_POINTER_INDEX_MASK&mACTION)>> ACTION_POINTER_MASK;
例如:mAction = 0x0105H (表示索引为1动作为:ACTION_DOWN,第一个手指按下)
则:index= (0xff00&0x0105) >> 0x00ff = 1;
二、MotionEvent示例
监听触摸事件(触摸每个点,以点为圆心生成一个不同颜色的小圆和一个交叉的十字,)
示例代码:
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // TODO Auto-generated method stub
- int pointerCount = event.getPointerCount();
- if (pointerCount > MAX_TOUCHPOINTS) {
- pointerCount = MAX_TOUCHPOINTS;
- }
- // 锁定Canvas开始进行相应的界面处理
- Canvas c =getHolder().lockCanvas();
- if (c != null) {
- c.drawColor(Color.BLACK);
- // 事件类型,也可以使用getActionMasked效果一样
- // int actionType = event.getAction()& MotionEvent.ACTION_MASK;
- int actionType = event.getActionMasked();
- switch (actionType) {
- case MotionEvent.ACTION_DOWN:
- // 第一个点被按下
- Toast.makeText(getContext(), "第一个点被按下", Toast.LENGTH_SHORT)
- .show();
- Toast.makeText(getContext(), "ACTION_POINT_DOWN",
- Toast.LENGTH_SHORT).show();
- // 现在屏幕上画一个十字,横向贯穿屏幕,纵向贯穿屏幕
- //获得触控事件的索引,通过索引获得Pointer的ID
- int id = event.getPointerId(event.getActionIndex());
- Toast.makeText(getContext(), "id = " + id, Toast.LENGTH_SHORT)
- .show();
- int x = (int) event.getX();
- int y = (int) event.getY();
- drawCrosshairsAndText(x, y, touchPaints[id], 0, id, c);
- drawCircle(x, y, touchPaints[id], c);
- break;
- case MotionEvent.ACTION_UP:
- // 最后一个点被释放
- Toast.makeText(getContext(), "最后一个点被释放", Toast.LENGTH_SHORT)
- .show();
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- // 非第一个点被按下
- Toast.makeText(getContext(), "非第一个点被按下", Toast.LENGTH_SHORT)
- .show();
- // 现在屏幕上画一个十字,横向贯穿屏幕,纵向贯穿屏幕
- for (int i = 0; i < pointerCount; i++) {
- // 获取一个触点的坐标,然后开始绘制
- int id1 = event.getPointerId(i);
- int x1 = (int) event.getX(i);
- int y1 = (int) event.getY(i);
- drawCrosshairsAndText(x1, y1, touchPaints[id1], i, id1, c);
- }
- // 使用不同的颜色在每个手指的位置画圆
- for (int i = 0; i < pointerCount; i++) {
- int id1 = event.getPointerId(i);
- Toast.makeText(getContext(),"id = " + id1,
- Toast.LENGTH_SHORT).show();
- int x1 = (int) event.getX(i);
- int y1 = (int) event.getY(i);
- drawCircle(x1, y1, touchPaints[id1], c);
- // DrawCircleRun drawCircleRun = new
- // DrawCircleRun(x,y,id,c);
- // mRunList[event.getActionIndex()] = drawCircleRun;
- // mThreadPool.execute(drawCircleRun);
- }
- break;
- case MotionEvent.ACTION_POINTER_UP:
- // 非最后一个点被释放
- //获得释放的点的索引和ID
- int point_index = event.getActionIndex();
- int point_id = event.getPointerId(point_index);
- Toast.makeText(getContext(), "非最后一个点被释放:"+"index ="+point_index+" id = "+point_id, Toast.LENGTH_SHORT)
- .show();
- break;
- case MotionEvent.ACTION_MOVE:
- Toast.makeText(getContext(), "手指移动", Toast.LENGTH_SHORT).show();
- // 现在屏幕上画一个十字,横向贯穿屏幕,纵向贯穿屏幕
- for (int i = 0; i < pointerCount; i++) {
- // 获取一个触点的坐标,然后开始绘制
- int id1 = event.getPointerId(i);
- int x1 = (int) event.getX(i);
- int y1 = (int) event.getY(i);
- drawCrosshairsAndText(x1, y1, touchPaints[id1], i, id1, c);
- }
- // 使用不同的颜色在每个手指的位置画圆
- for (int i = 0; i < pointerCount; i++) {
- int id1 = event.getPointerId(i);
- Toast.makeText(getContext(),"id = " + id1,
- Toast.LENGTH_SHORT).show();
- int x1 = (int) event.getX(i);
- int y1 = (int) event.getY(i);
- drawCircle(x1, y1, touchPaints[id1], c);
- // DrawCircleRun drawCircleRun = new
- // DrawCircleRun(x,y,id,c);
- // mRunList[event.getActionIndex()] = drawCircleRun;
- // mThreadPool.execute(drawCircleRun);
- }
- break;
- case MotionEvent.ACTION_SCROLL:
- Toast.makeText(getContext(), "手指滑动", Toast.LENGTH_SHORT).show();
- break;
- case MotionEvent.ACTION_POINTER_2_DOWN:
- Toast.makeText(getContext(), "第二个手指按下", Toast.LENGTH_SHORT)
- .show();
- break;
- case MotionEvent.ACTION_POINTER_3_DOWN:
- Toast.makeText(getContext(), "第三个手指按下", Toast.LENGTH_SHORT)
- .show();
- break;
- case MotionEvent.ACTION_POINTER_3_UP:
- Toast.makeText(getContext(), "第三个手指离开", Toast.LENGTH_SHORT)
- .show();
- break;
- default:
- break;
- }
- }
- // 画完后,解锁显示
- getHolder().unlockCanvasAndPost(c);
- return true;
- }
三、为什么要使用掩码和位操作?
很多人,我一开始也疑惑,为什么不从一开始就使用两个整型来存储动作和索引信息呢。这样不是更容易让人理解吗?不过看了API文档的解释之后,才明白这是为了节省内存。因为动作就那么几个,位置信息在高八位以上,还有24位的信息用来存储索引信息(2的24次方个点,绝对够用!)。因此只需要一个32位的整型就可以存储这两个信息。而且计算机内部也只是识别 0\1序列的,我们搞IT的免不了会接触到一些汇编,汇编语言里面的知识在这里就派上用场了,比如:移位、按位逻辑运算等知识。而且熟悉了位存储之后,对于Android的其他类别属性的理解也会更加有帮助。