处理多点触控手势

多点触控手势是指多个指针(手指)同时轻触屏幕。这节课介绍了如何检测涉及多个指针的手势。

请参阅以下相关资源:

跟踪多个指针

当多个指针同时轻触屏幕时,系统会生成以下触摸事件:

  • ACTION_DOWN - 针对轻触屏幕的第一个指针。这是手势的起点。此指针的指针数据始终位于 MotionEvent 中的索引 0 处。
  • ACTION_POINTER_DOWN - 针对除第一个指针以外进入屏幕的其他指针。此指针的指针数据位于 getActionIndex() 返回的索引处。
  • ACTION_MOVE - 在执行按下手势的过程中发生了变化。
  • ACTION_POINTER_UP - 当非主要指针抬起时发送。
  • ACTION_UP - 当最后一个指针离开屏幕时发送。

您可以通过每个指针的索引和 ID 跟踪 MotionEvent 中的各个指针:

  • 索引MotionEvent 会有效地将关于每个指针的信息存储在数组中。指针的索引就是指针在此数组中的位置。用于与指针进行交互的大多数 MotionEvent 方法都使用触控点索引(而非指针 ID)作为参数。
  • ID:每个指针还有一个 ID 映射,该映射在触摸事件之间保持不变,让您能够在整个手势中跟踪单个指针。

单个指针在动作事件中的显示顺序是未定义的。因此,不同事件中的触控点索引可能会不一样,但只要指针保持活动状态,其指针 ID 就会保持不变。您可以使用 getPointerId() 方法获取指针的 ID,并在手势中的所有后续动作事件中跟踪指针。然后,对于连续动作事件,可以使用 findPointerIndex() 方法获取给定指针 ID 在相应动作事件中的触控点索引。例如:

Kotlin

    private var mActivePointerId: Int = 0

    override fun onTouchEvent(event: MotionEvent): Boolean {
        ...
        // Get the pointer ID
        mActivePointerId = event.getPointerId(0)

        // ... Many touch events later...

        // Use the pointer ID to find the index of the active pointer
        // and fetch its position
        val (x: Float, y: Float) = event.findPointerIndex(mActivePointerId).let { pointerIndex ->
            // Get the pointer's current position
            event.getX(pointerIndex) to event.getY(pointerIndex)
        }
        ...
    }
    

Java

    private int mActivePointerId;

    public boolean onTouchEvent(MotionEvent event) {
        ...
        // Get the pointer ID
        mActivePointerId = event.getPointerId(0);

        // ... Many touch events later...

        // Use the pointer ID to find the index of the active pointer
        // and fetch its position
        int pointerIndex = event.findPointerIndex(mActivePointerId);
        // Get the pointer's current position
        float x = event.getX(pointerIndex);
        float y = event.getY(pointerIndex);
        ...
    }
    

获取 MotionEvent 的操作

您应该始终使用方法 getActionMasked()(最好使用兼容版本 MotionEventCompat.getActionMasked())来检索 MotionEvent 的操作。与旧版 getAction() 方法不同,getActionMasked() 能够与多个指针一起使用。它可以返回正在执行的已遮蔽操作,而不包含触控点索引位。然后,您可以使用 getActionIndex() 返回与此操作相关联的指针的索引。以下代码段对此进行了说明。

注意:此示例使用的是 MotionEventCompat 类。此类包含在支持库中。您应该使用 MotionEventCompat 为各种平台提供最佳支持。请注意,MotionEventCompat 不能替代 MotionEvent 类。相反,它可以提供静态实用程序方法,您可以向这些方法传递 MotionEvent 对象,以便接收与相应事件相关的预期操作。

Kotlin

    val (xPos: Int, yPos: Int) = MotionEventCompat.getActionMasked(event).let { action ->
        Log.d(DEBUG_TAG, "The action is ${actionToString(action)}")
        // Get the index of the pointer associated with the action.
        MotionEventCompat.getActionIndex(event).let { index ->
            // The coordinates of the current screen contact, relative to
            // the responding View or Activity.
            MotionEventCompat.getX(event, index).toInt() to MotionEventCompat.getY(event, index).toInt()
        }
    }

    if (event.pointerCount > 1) {
        Log.d(DEBUG_TAG, "Multitouch event")

    } else {
        // Single touch event
        Log.d(DEBUG_TAG, "Single touch event")
    }

    ...

    // Given an action int, returns a string description
    fun actionToString(action: Int): String {
        return when (action) {
            MotionEvent.ACTION_DOWN -> "Down"
            MotionEvent.ACTION_MOVE -> "Move"
            MotionEvent.ACTION_POINTER_DOWN -> "Pointer Down"
            MotionEvent.ACTION_UP -> "Up"
            MotionEvent.ACTION_POINTER_UP -> "Pointer Up"
            MotionEvent.ACTION_OUTSIDE -> "Outside"
            MotionEvent.ACTION_CANCEL -> "Cancel"
            else -> ""
        }
    }
    

Java

    int action = MotionEventCompat.getActionMasked(event);
    // Get the index of the pointer associated with the action.
    int index = MotionEventCompat.getActionIndex(event);
    int xPos = -1;
    int yPos = -1;

    Log.d(DEBUG_TAG,"The action is " + actionToString(action));

    if (event.getPointerCount() > 1) {
        Log.d(DEBUG_TAG,"Multitouch event");
        // The coordinates of the current screen contact, relative to
        // the responding View or Activity.
        xPos = (int)MotionEventCompat.getX(event, index);
        yPos = (int)MotionEventCompat.getY(event, index);

    } else {
        // Single touch event
        Log.d(DEBUG_TAG,"Single touch event");
        xPos = (int)MotionEventCompat.getX(event, index);
        yPos = (int)MotionEventCompat.getY(event, index);
    }
    ...

    // Given an action int, returns a string description
    public static String actionToString(int action) {
        switch (action) {

            case MotionEvent.ACTION_DOWN: return "Down";
    	case MotionEvent.ACTION_MOVE: return "Move";
    	case MotionEvent.ACTION_POINTER_DOWN: return "Pointer Down";
    	case MotionEvent.ACTION_UP: return "Up";
    	case MotionEvent.ACTION_POINTER_UP: return "Pointer Up";
    	case MotionEvent.ACTION_OUTSIDE: return "Outside";
    	case MotionEvent.ACTION_CANCEL: return "Cancel";
        }
        return "";
    }
    

如需详细了解多点触控和一些示例,请参阅拖动和缩放课程。