android开发游记:多点触控解析与运用
2016-05-30 14:31
555 查看
andorid 自2.0以后加入了对多点触控的支持,而多点触控的使用,在多数应用中是用不到的,或者用不用区别不大,但是在某些需要拖拽的控件中加入多点触控的支持会有更好的用户体验,最常见的比如下拉刷新,美团、京东、百度糯米、阿里旅行等应用都没有处理多点触控的情况,因此在拉动的时候用2只手连续拉动就会出现页面闪动的情况。毕竟只有少数闲得蛋疼的用户才会这样去拖拽,处不处理区别不大,但是在一些更加复杂的拖拽场合中多点触控的处理就很有必要了,比如qq的下拉抢红包。带着学习的态度,还是很有必要掌握的。
我们在触摸事件中可以得到MotionEvent对象,
先介绍MotionEvent的几个常用方法:
这个方法可以获取触摸点个数,一般来说处理两个点就行了,3点的情况实在太少
这个方法用于获取触摸事件的触摸状态,我们平时使用的getAction()方法是无法捕获多点触控的情况的,使用getActionMasked()就额外可以捕获到ACTION_POINTER_DOWN和ACTION_POINTER_UP事件
当屏幕上已经有一个点被按住,此时再按下其他点时触发。
MotionEvent.ACTION_POINTER_UP:
当屏幕上有多个点被按住,松开其中一个点时触发(即非最后一个点被放开时)。
我们得到的MotionEvent对象维护了一个有序的触点序列,这个序列记录了当前屏幕中的所有触点,我们可以通过index下标获取指定触点的信息,比如MotionEvent.getX()可以获取触摸点的X坐标,它还提供了一个> 重载方法MotionEvent.getX(index),获取指定触点的X坐标。
而这个有序序列的位置并不是不变的,根据用户触摸的顺序,序列的顺序是不断变化的,也就是说你使用一个下标在同一个触摸事件中两次获取到的不一定是同一个触点,为了解决这个问题系统为每个触点关联了唯一的ID,而这个ID是固定不变的,我们获取指定触点的思路就变成了 :1.使用ID获取index,2.再用index获取触点信息
如下通过一个ID获取指定触点的XY坐标:
上面的代码还可以写成:
MotionEventCompat是v4中引入的一个兼容低版本(API 8)的类,用于兼容低版本的MotionEvent,使用方法一> 致,后面的介绍都统一使用MotionEventCompat
效果图:(注意这是两只手连续拖拽)
附上下载地址: demo下载地址
首先创建一个TestView继承自View:
重写dispatchTouchEvent,在里面调用一个用于处理多点触控的自定义方法dealMulTouchEvent
首先,在ACTION_DOWN的时候(第一只手按下),我们获取触点的x,y坐标,保存在mLastX和mLastY中,并获取当前触点ID,记作活动点,保存在一个类变量中:
接着,在ACTION_MOVE的时候,我们通过活动点ID获取活动点的坐标,与上一次的坐标mLastX、mLastY进行差运算,计算横纵轴的拖拽距离dx、dy:
当用户另一只手再次按下时(触发第二个触点),触发ACTION_POINTER_DOWN事件,如果触发的点不是活动点的话,就更新mLastX 、mLastY,并设其为活动点:
当用户两只手都在触摸事件中,这时松开其中一种手(不管是不是活动点的那一只手),触发ACTION_POINTER_UP事件,判断,如果松开的手是活动点的那一只手,就更新mLastX 、mLastY,并把另一触点设为活动点:
当用户松开另一只手时(此时只有一只手再触摸),触发ACTION_UP,我们清理活动点,设置为“无”
ACTION_CANCEL 也一同处理
以上就是多点触控的核心代码了。
总结一下思路:
在ACTION_DOWN中设置初始的活动点和位置信息,在多点按下和多点松开的事件ACTION_POINTER_DOWN、ACTION_POINTER_UP中根据用户按下和松开的情况,更新活动点和位置信息,始终保证最后按下和最后松开的点为活动点,在ACTION_MOVE中始终获取活动点的位置信息来计算拖拽距离,最后在ACTION_UP和ACTION_CANCEL中清除活动点。
剩下的就是拖拽和复位的功能:
重新onTouchEvent,在ACTION_MOVE中进行拖拽,在ACTION_UP和ACTION_CANCEL进行复位:
demo下载地址
我们在触摸事件中可以得到MotionEvent对象,
先介绍MotionEvent的几个常用方法:
获取当前屏幕触摸点个数
getPointerCount()这个方法可以获取触摸点个数,一般来说处理两个点就行了,3点的情况实在太少
获取的触摸状态
getActionMasked()这个方法用于获取触摸事件的触摸状态,我们平时使用的getAction()方法是无法捕获多点触控的情况的,使用getActionMasked()就额外可以捕获到ACTION_POINTER_DOWN和ACTION_POINTER_UP事件
多点触控的特殊事件
MotionEvent.ACTION_POINTER_DOWN:当屏幕上已经有一个点被按住,此时再按下其他点时触发。
MotionEvent.ACTION_POINTER_UP:
当屏幕上有多个点被按住,松开其中一个点时触发(即非最后一个点被放开时)。
每个触点分类处理
对触点进行分类处理是多点触控的核心思想,这里提供了3个方法帮助我们获取我们想要的触点://获取当前触点的index下标 getActionIndex() //获取触点唯一ID getPointerId() //通过触点的ID获取index下标 findPointerIndex()
我们得到的MotionEvent对象维护了一个有序的触点序列,这个序列记录了当前屏幕中的所有触点,我们可以通过index下标获取指定触点的信息,比如MotionEvent.getX()可以获取触摸点的X坐标,它还提供了一个> 重载方法MotionEvent.getX(index),获取指定触点的X坐标。
而这个有序序列的位置并不是不变的,根据用户触摸的顺序,序列的顺序是不断变化的,也就是说你使用一个下标在同一个触摸事件中两次获取到的不一定是同一个触点,为了解决这个问题系统为每个触点关联了唯一的ID,而这个ID是固定不变的,我们获取指定触点的思路就变成了 :1.使用ID获取index,2.再用index获取触点信息
如下通过一个ID获取指定触点的XY坐标:
//MotionEvent ev int pointerIndex = ev.findPointerIndex(ev, mActivePointerId); float x = ev.getX(pointerIndex); float y = ev.getY(pointerIndex);
上面的代码还可以写成:
//MotionEvent ev int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); float x = MotionEventCompat.getX(ev, pointerIndex); float y = MotionEventCompat.getY(ev, pointerIndex);
MotionEventCompat是v4中引入的一个兼容低版本(API 8)的类,用于兼容低版本的MotionEvent,使用方法一> 致,后面的介绍都统一使用MotionEventCompat
示例
为了更好地理解多点触控原理,我们来做一个例子:自定义一个View可以双手连续拖拽,松开后弹回到原位效果图:(注意这是两只手连续拖拽)
附上下载地址: demo下载地址
首先创建一个TestView继承自View:
public class TestView extends View{ public TestView(Context context, AttributeSet attrs) { super(context, attrs); } }
重写dispatchTouchEvent,在里面调用一个用于处理多点触控的自定义方法dealMulTouchEvent
@Override public boolean dispatchTouchEvent(MotionEvent event) { dealMulTouchEvent(event); return super.dispatchTouchEvent(event); }
多点触控分类处理
使用getActionMasked获取触摸事件进行分类处理:public void dealMulTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); switch (action) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_POINTER_DOWN: break; case MotionEvent.ACTION_POINTER_UP: break; } }
首先,在ACTION_DOWN的时候(第一只手按下),我们获取触点的x,y坐标,保存在mLastX和mLastY中,并获取当前触点ID,记作活动点,保存在一个类变量中:
case MotionEvent.ACTION_DOWN: { final int pointerIndex = MotionEventCompat.getActionIndex(ev); final float x = MotionEventCompat.getX(ev, pointerIndex); final float y = MotionEventCompat.getY(ev, pointerIndex); // 记录开始拖拽时的坐标 (按下的坐标) mLastX = x; mLastY = y; // 记录当前触摸点的ID(活动点的ID) mActivePointerId = MotionEventCompat.getPointerId(ev, 0); break; }
接着,在ACTION_MOVE的时候,我们通过活动点ID获取活动点的坐标,与上一次的坐标mLastX、mLastY进行差运算,计算横纵轴的拖拽距离dx、dy:
case MotionEvent.ACTION_MOVE: { // 获取活动点坐标 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, pointerIndex); final float y = MotionEventCompat.getY(ev, pointerIndex); // 计算拖拽距离 dy = y - mLastY; dx = x - mLastX; // 更新上次保存的坐标 mLastX = x; mLastY = y; break; }
当用户另一只手再次按下时(触发第二个触点),触发ACTION_POINTER_DOWN事件,如果触发的点不是活动点的话,就更新mLastX 、mLastY,并设其为活动点:
case MotionEvent.ACTION_POINTER_DOWN: { //获取再次按下的触点的ID final int pointerIndex = MotionEventCompat.getActionIndex(ev); final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); //如果触发的点不是活动点的话,就更新mLastX/mLastY,并设其为活动点 if (pointerId != mActivePointerId) { mLastX = MotionEventCompat.getX(ev, pointerIndex); mLastY = MotionEventCompat.getY(ev, pointerIndex); mActivePointerId = MotionEventCompat.getPointerId(ev, pointerIndex); } break; }
当用户两只手都在触摸事件中,这时松开其中一种手(不管是不是活动点的那一只手),触发ACTION_POINTER_UP事件,判断,如果松开的手是活动点的那一只手,就更新mLastX 、mLastY,并把另一触点设为活动点:
case MotionEvent.ACTION_POINTER_UP: { //获取松开的触点的ID final int pointerIndex = MotionEventCompat.getActionIndex(ev); final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); //如果松开的是活动点,则把另一个点设为活动点,并更新mLastX/mLastY if (pointerId == mActivePointerId) { final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastX = MotionEventCompat.getX(ev, newPointerIndex); mLastY = MotionEventCompat.getY(ev, newPointerIndex); mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); } break; }
当用户松开另一只手时(此时只有一只手再触摸),触发ACTION_UP,我们清理活动点,设置为“无”
ACTION_CANCEL 也一同处理
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mActivePointerId = MotionEvent.INVALID_POINTER_ID; break;
以上就是多点触控的核心代码了。
总结一下思路:
在ACTION_DOWN中设置初始的活动点和位置信息,在多点按下和多点松开的事件ACTION_POINTER_DOWN、ACTION_POINTER_UP中根据用户按下和松开的情况,更新活动点和位置信息,始终保证最后按下和最后松开的点为活动点,在ACTION_MOVE中始终获取活动点的位置信息来计算拖拽距离,最后在ACTION_UP和ACTION_CANCEL中清除活动点。
剩下的就是拖拽和复位的功能:
重新onTouchEvent,在ACTION_MOVE中进行拖拽,在ACTION_UP和ACTION_CANCEL进行复位:
@Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: doMove(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: resetPosition(); break; } return true; }
拖拽和复位
最后给出拖拽和复位的方法,拖拽是通过layout方法来进行位移,复位则是利用位移动画来完成,没什么难点,不作介绍了。//拖拽方法 private void doMove(){ if (mRect.isEmpty()) { mRect.set(getLeft(), getTop(), getRight(), getBottom()); } int movedx = (int) (dx/2); int movedy = (int) (dy/2); if (movedy!=0) { layout(getLeft() + movedx, getTop() + movedy, getLeft()+getWidth()+movedx, getTop() + getHeight() + movedy); } } //复位方法 private void resetPosition() { Animation animation = new TranslateAnimation(getLeft()-mRect.left, 0, getTop()-mRect.top,0); animation.setDuration(200); animation.setFillAfter(true); startAnimation(animation); layout(mRect.left, mRect.top, mRect.right, mRect.bottom); mRect.setEmpty(); }
下载
最后附上demo下载地址:demo下载地址
相关文章推荐
- Android多点触控实现对图片放大缩小平移,惯性滑动等功能
- unity实现多点触控代码
- android 电容屏多点触控协议
- cocos2d-x 3.9 多点触控之iOS监听无效(只能监听到单点触控)
- android多点触控初试
- 基于ImageView的多点触控,双击放大缩小以及结合ViewPager的事件冲突
- [yueqian_scut]Android多点触控技术和应用框架
- Android同时按下多个按钮
- Android多点触控MultiTouch浅析
- android开发之多点触摸交互处理
- Android 多点触控技术
- Muilti-touch 双指缩放的实现探索
- 关于cocos2dx多点触控的问题
- Android多点触控技术实战,自由地对图片进行缩放和移动
- android多点触控统一的原理(使用 event.getAction()&MotionEvent.ACTION_MASK的原因)
- 多点触控事件处理 ev.getAction() & MotionEvent.ACTION_MASK
- 多点触控及一个华丽的Demo
- Android-图片预览(自定义ImageView实现图片缩放,多点触控,自由移动)
- cocos2d-x+lua 实现两点触控缩放
- cocos2dx支持多点触控