Android 多点触控教程

2,405 阅读2分钟

MotionEvent.getActionMaskde()

常见值有:

  1. ACTION_DOWN 第一个手指按下(之前没有任何手指触摸到View)
  2. ACTION_UP 最后一个手指抬起(抬起后没有手指触摸View,这个手指未必是ACTION_DOWN)
  3. ACTION_MOVE 有手指所发生移动
  4. ACTION_POINTEER_DOWN 额外手指按下(按下之前已经有别的手指触摸到View)
  5. ACTION_POINTER_UP 有手指抬起,但不是最后一个(抬起之后,仍然有别的手指在触摸着View)

触摸事件的结构

  1. 触摸事件是按序列来分组的,每一组事件必然以ACTION_DOWN开头,以ACTION_UP或ACTION_CANCEL结束
  2. ACTION_POINTER_DOWN和ACTION_MOVE一样,只是事件序列组成部分,并不会单独分出新的事件序列
  3. 触摸事件序列是针对View的,而不是针对pointer的。
  4. 同一个时刻,一个View要么没有事件序列,要么只有一个事件序列

多点触控的三种类型

1. 接力型

同一时刻只有一个pointer起作用,即最新的pointer。如listview、recyclerview。实现方式:在ACTION_POINTER_DOWN和ACTION_POINTER_UP时记录下最新的pointer,在之后的ACTION_MOVE事件中使用这个pointer来判断位置。

         case MotionEvent.ACTION_DOWN:
            //拿到第0跟手指
            trackID = event.getPointerId(0);
            downX = event.getX();
            downY = event.getY();
            originalOffsetX = offsetX;
            originalOffsetY = offsetY;
            break;
        case MotionEvent.ACTION_MOVE:
            int index = event.findPointerIndex(trackID);
            offsetX = originalOffsetX + event.getX(index) - downX;
            offsetY = originalOffsetY + event.getY(index) - downY;
            invalidate();
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            int actionIndex = event.getActionIndex();
            trackID = event.getPointerId(actionIndex);
            downX = event.getX(actionIndex);
            downY = event.getY(actionIndex);
            originalOffsetX = offsetX;
            originalOffsetY = offsetY;
            break;

        case MotionEvent.ACTION_POINTER_UP:
            //抬起来的这根手指的index
            actionIndex = event.getActionIndex();
            int pointerId = event.getPointerId(actionIndex);
            if (pointerId == trackID) {
                //将当前的点分配到这根手指
                int newIndex;
                //如果抬起来的这根手指恰好是最后一根则
                if (actionIndex == event.getPointerCount() - 1) {
                    newIndex = event.getPointerCount() - 2;
                } else {
                    //如果是其他手指则
                    newIndex = event.getPointerCount() - 1;
                }
                trackID = event.getPointerId(newIndex);
                downX = event.getX(newIndex);
                downY = event.getY(newIndex);
                originalOffsetX = offsetX;
                originalOffsetY = offsetY;
            }
            break;

2. 配合型/协作型

所有触摸到View的pointer共同起作用。如:ScaleGestureDetector,以及GestureDetector的onScroll()方法判断。实现方式:在每个DOWN、POINTER_DOWN、POINTER_UP、UP事件中使用所有的pointer的坐标来共同更新焦点坐标,在MOVE事件中使用所有的pointer的坐标来判断位置。

        float sumX = 0;
        float sumY = 0;
        int pointerCount = event.getPointerCount();
        boolean isPointerUp = event.getActionMasked() == MotionEvent.ACTION_POINTER_UP;
        for (int i = 0; i < pointerCount; i++) {
            if (!(isPointerUp && i == event.getActionIndex())) {
                sumX += event.getX(i);
                sumY += event.getY(i);
            }
        }
        if (isPointerUp) {
            pointerCount -= 1;
        }
        float focusX = sumX / pointerCount;
        float focusY = sumY / pointerCount;
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
            case MotionEvent.ACTION_POINTER_UP:
                downX = focusX;
                downY = focusY;
                originalOffsetX = offsetX;
                originalOffsetY = offsetY;
                break;
            case MotionEvent.ACTION_MOVE:
                offsetX = originalOffsetX + focusX - downX;
                offsetY = originalOffsetY + focusY - downY;
                invalidate();
                break;
        }
3. 各自独立型

各个pointer做不同的事,互不影响。典型:支持多画笔的画板应用。实现方式:在每个DOWN、POINTER_DOWN事件中记录下每个pointer的id,在MOVE事件中使用id对他们进行追踪。

        case ACTION_DOWN:
        case ACTION_POINTER_DOWN:
            int actionIndex = event.getActionIndex();
            int pointerId = event.getPointerId(actionIndex);
            Path path = new Path();
            path.moveTo(event.getX(actionIndex), event.getY(actionIndex));
            paths.append(pointerId, path);
            invalidate();
            break;
        case ACTION_MOVE:
            for (int i = 0; i < event.getPointerCount(); i++) {
                pointerId = event.getPointerId(i);
                path = paths.get(pointerId);
                path.lineTo(event.getX(i), event.getY(i));
            }
            invalidate();
            break;
        case ACTION_UP:
        case ACTION_POINTER_UP:
            pointerId = event.getPointerId(event.getActionIndex());
            paths.remove(pointerId);
            invalidate();
            break;