Cómo controlar gestos multitáctiles

Un gesto multitáctil se produce cuando varios punteros (dedos) tocan la pantalla al mismo tiempo. En esta lección, se describe cómo detectar gestos que involucran varios punteros.

Consulta los siguientes recursos relacionados:

Cómo rastrear varios punteros

Cuando varios punteros tocan la pantalla al mismo tiempo, el sistema genera los siguientes eventos táctiles:

  • ACTION_DOWN para el primer puntero que toca la pantalla. Este evento comienza el gesto. Los datos del puntero para este puntero siempre están en el índice 0 de MotionEvent.
  • ACTION_POINTER_DOWN para punteros adicionales que ingresan a la pantalla además del primero. Los datos del puntero para este puntero están en el índice que muestra getActionIndex().
  • ACTION_MOVE: Se produjo un cambio durante un gesto de prensa.
  • ACTION_POINTER_UP: Se envía cuando un puntero que no es principal va hacia arriba.
  • ACTION_UP: Se envía cuando el último puntero sale de la pantalla.

Puedes rastrear los punteros individuales dentro de un MotionEvent mediante el índice y el ID de cada puntero:

  • Índice: Un MotionEvent almacena de manera eficaz información sobre cada puntero en un arreglo. El índice de un puntero es su posición dentro de este arreglo. La mayoría de los métodos MotionEvent que usas para interactuar con punteros toman el índice del puntero como un parámetro, no el ID del puntero.
  • ID: Cada puntero también tiene una asignación de ID que se mantiene constante en los eventos táctiles para permitir el rastreo de un puntero individual en todo el gesto.

El orden en que aparecen los punteros individuales dentro de un evento de movimiento no está definido. Por lo tanto, el índice de un puntero puede cambiar de un evento al siguiente, pero se garantiza que el ID del puntero de un puntero permanecerá constante mientras el puntero esté activo. Usa el método getPointerId() a fin de obtener un ID de puntero para rastrear el puntero en todos los eventos de movimiento posteriores en un gesto. Luego, para eventos de movimiento sucesivos, usa el método findPointerIndex() a fin de obtener el índice del puntero de un ID de puntero determinado en ese evento de movimiento. Por ejemplo:

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);
        ...
    }
    

Cómo obtener la acción de MotionEvent

Siempre debes usar el método getActionMasked() (o, mejor aún, la versión de compatibilidad MotionEventCompat.getActionMasked()) para recuperar la acción de un MotionEvent. A diferencia del método getAction() antiguo, getActionMasked() está diseñado para funcionar con varios indicadores. Muestra la acción enmascarada que se realiza, sin incluir los bits de índice del puntero. Luego, puedes usar getActionIndex() para mostrar el índice del puntero asociado con la acción. Esto se ilustra en el siguiente fragmento.

Nota: En este ejemplo, se usa la clase MotionEventCompat, que se encuentra en la biblioteca de compatibilidad. Debes usar MotionEventCompat para proporcionar la mejor compatibilidad con una amplia gama de plataformas. Ten en cuenta que MotionEventCompat no reemplaza a la clase MotionEvent. En cambio, proporciona métodos de utilidades estáticos que puedes pasar al objeto MotionEvent para recibir la acción deseada asociada con ese evento.

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 "";
    }
    

Para obtener más información sobre la función multitáctil y ver algunos ejemplos, consulta la lección Arrastre y ajuste de tamaño.