android开发游记:多点触控解析与运用

andorid 自2.0以后加入了对多点触控的支持,而多点触控的使用,在多数应用中是用不到的,或者用不用区别不大,但是在某些需要拖拽的控件中加入多点触控的支持会有更好的用户体验,最常见的比如下拉刷新,美团、京东、百度糯米、阿里旅行等应用都没有处理多点触控的情况,因此在拉动的时候用2只手连续拉动就会出现页面闪动的情况。毕竟只有少数闲得蛋疼的用户才会这样去拖拽,处不处理区别不大,但是在一些更加复杂的拖拽场合中多点触控的处理就很有必要了,比如qq的下拉抢红包。带着学习的态度,还是很有必要掌握的。

我们在触摸事件中可以得到MotionEvent对象,
先介绍MotionEvent的几个常用方法:

获取当前屏幕触摸点个数

getPointerCount()
这个方法可以获取触摸点个数,一般来说处理两个点就行了,3点的情况实在太少

获取的触摸状态

getActionMasked()

这个方法用于获取触摸事件的触摸状态,我们平时使用的getAction()方法是无法捕获多点触控的情况的,使用getActionMasked()就额外可以捕获到ACTION_POINTER_DOWNACTION_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下载地址

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值