多点触摸基本概念

导言

Android支持多点触摸事件,平时比较常见的可能就是放大和缩小手势,其次常见的可能就是自定义一些滑动视图,为了避免一些“意外”出现(比方说瞬移),还是考虑一下多点触摸

基本概念

Android对于事件的概念都封装在了MotionEvent这个类中,首先了解一些基本的信息

public static final int ACTION_MASK             = 0xff;
public static final int ACTION_POINTER_INDEX_MASK  = 0xff00;
public static final int ACTION_POINTER_INDEX_SHIFT = 8;

public final int getAction() {
    return nativeGetAction(mNativePtr);
}

public final int getActionMasked() {
    return nativeGetAction(mNativePtr) & ACTION_MASK;
}

public final int getActionIndex() {
    return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)
                >> ACTION_POINTER_INDEX_SHIFT;
}

对比上面的三个方法,举个例子看看:
getAction()本身返回的是一个32位的int,即
0000 0001 0000 0011
getActionMasked()进行了低8位与运算,从而结果只会剩下低8位
0000 0000 0000 0011
getActionIndex()进行了高8位与运算,然后再右移8位,相当于把高8位移到低8位,然后高8位填0
0000 0001 0000 0000
0000 0000 0000 0001

从上述计算可以看出,实际上getAction()返回的是ActionIndex和事件类型的组合
ActionIndex:
取值为0、1、2...N,在手指的移出后Index还会进行自动调整,始终保持0、1...手指数量-1这个序列,也就是说index=0只能标识有一个手指,但是不能说明这个手指一定是第一次按下的手指,同样也可以是从两根手指的情况下,松开第一次按下的手指从而剩下的那个手指
对应于这个还有一个概念

public final int getPointerId(int pointerIndex) {
    return nativeGetPointerId(mNativePtr, pointerIndex);
}

pointerId:
区别于ActionIndex,pointerId能够做到保证同一手指的值不变,也就是说可以通过pointerId来标识具体的手指

小结

方法 描述
getAction() 获得ActionIndex和事件类型组合的值,当且仅当只有一个手指,此时获得的是事件类型,那么也就不能处理多点触摸
getActionMasked() 获得事件类型
getActionIndex() 获得事件Index

个人来说,推荐在OnTouchEvent中使用getActionMasked()来判断事件类型,这样才是符合MotionEvent的设计原理

多点触摸实践

我们知道处理触摸事件的逻辑是在onTouchEvent(MotionEvent event)中处理
也就是说每一次只能处理单独的一个MotionEvent事件
这里考虑一个滑动视图,比方说
https://github.com/dda135/PullRefreshLayout这一个布局刷新自定义控件

首先明确几个概念:
1.滑动的时候拦截事件的可能是任何一根手指
2.滑动以最后一个按下来的手指为准
3.最后一个滑动的手指松开之后,后续的滑动应该尽量以这个手指之前按下的最后一个手指为准,如果这个手指之前已经松开,那么再往前追溯

概念明确之后,实现思路也就清晰了:
1.应该记录当前最后一个手指的pointerId
2.应该提供一个列表存储按照手指按下的顺序存储pointerId

private static final int INVALID_POINTID = -1;
//用于记录上一次事件的坐标
private Point mLastPoint;
//用于记录当前最后一个手指的pointerId
private int mActivePointId;
//用于按顺序存储按下手指的pointerId
private List mPointIdList = new ArrayList<>();

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        ...
        int pointerIndex = event.getActionIndex();
        int pointerId = event.getPointerId(pointerIndex);
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
                mActivePointId = pointerId;
                mLastPoint.set((int) event.getX(pointerIndex), (int) event.getY(pointerIndex));
                mPointIdList.add(pointerId);
                break;
            case MotionEvent.ACTION_MOVE:
                int activeIndex = event.findPointerIndex(mActivePointId);
                if(activeIndex < 0){
                    return false;
                }
                int x = (int) event.getX(activeIndex);
                int y = (int) event.getY(activeIndex);
                int deltaY = (y - mLastPoint.y);
                int dy = Math.abs(deltaY);
                int dx = Math.abs(x - mLastPoint.x);     
                if (dy > mTouchSlop && dy >= dx) {
                    canUp = mOption.canUpToDown();
                    canDown = mOption.canDownToUp();
                    canUpIntercept = (deltaY > 0 && canUp);
                    canDownIntercept = (deltaY < 0 && canDown);
                    return canUpIntercept || canDownIntercept;
                }
                return false;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mActivePointId = INVALID_POINTID;
                mPointIdList.clear();
                break;
            case MotionEvent.ACTION_POINTER_UP:
                resetTouch(event);
                break;
            default:
                break;
        }
        return false;
    }

    /**
     * 通过列表结构进行手指回溯操作
     * @param event 当前事件对象
     */
    private void resetTouch(MotionEvent event){
        int currentReleaseIndex = event.getActionIndex();
        int currentReleaseId = event.getPointerId(currentReleaseIndex);
        mPointIdList.remove((Integer) currentReleaseId);//先移除当前松开的手指
        while(mPointIdList.size() > 0){
            mActivePointId = mPointIdList.get(mPointIdList.size() - 1);
            int pointIndex = event.findPointerIndex(mActivePointId);
            if (pointIndex < 0){//当前Id无效,废弃
                mPointIdList.remove(mPointIdList.size() - 1);
                continue;
            }
            mLastPoint.set((int)event.getX(pointIndex),(int)event.getY(pointIndex));//当前活动手指变化,记录当前活动的最新坐标
            return;
        }
        mActivePointId = INVALID_POINTID;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        ...
        int pointIndex = event.getActionIndex();
        int pointId = event.getPointerId(pointIndex);
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
                mActivePointId = pointId;
                mLastPoint.set((int)event.getX(pointIndex),(int)event.getY(pointIndex));
                mPointIdList.add(mActivePointId);
                break;
            case MotionEvent.ACTION_MOVE:
                int activePointIndex = event.findPointerIndex(mActivePointId);
                if(activePointIndex < 0){
                    return false;
                }
                isOnTouch = true;
                updatePos((int) (mOption.getMoveRatio() * (event.getY(activePointIndex) - mLastPoint.y)));
                mLastPoint.set((int)event.getX(activePointIndex),(int)event.getY(activePointIndex));
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mActivePointId = INVALID_POINTID;
                mPointIdList.clear();
                ...
                break;
            case MotionEvent.ACTION_POINTER_UP:
                resetTouch(event);
                break;
            default:
                break;
        }
        return true;
    }

实际上思路很简单,当手指按下的时候(单指或多指),标记当前手指为活动手指,MOVE事件的时候以活动手指的移动为准,当有手指松开的时候,尝试寻找新的活动手指即可。

总结

Android源码的一些例子可以参考RecyclerView等滑动视图的源码,里面也有一些类似的操作,多点触摸实际上和单点触摸差不多,只要明确基本概念,处理起来也会相对轻松

你可能感兴趣的:(多点触摸基本概念)