Gerenciar vários gestos de toque

Teste o Compose
O Jetpack Compose é o kit de ferramentas de interface recomendado para Android. Aprenda a usar o toque e a entrada no Compose.

Um gesto multitoque ocorre quando vários ponteiros (dedos) tocam na tela ao mesmo tempo. Este documento descreve como detectar gestos que envolvem vários ponteiros.

Rastrear vários ponteiros

Quando vários ponteiros tocam na tela ao mesmo tempo, o sistema gera os seguintes eventos de toque:

  • ACTION_DOWN: enviado quando o primeiro ponteiro toca na tela. Isso inicia o gesto. Os dados desse ponteiro estão sempre no índice 0 do MotionEvent.
  • ACTION_POINTER_DOWN: enviado quando ponteiros extras entram na tela após o primeiro. Você pode ver o índice do ponteiro que acabou de ser reduzido usando getActionIndex().
  • ACTION_MOVE: enviado quando ocorre uma mudança em um gesto, envolvendo qualquer número de ponteiros.
  • ACTION_POINTER_UP: enviado quando um ponteiro não primário sobe. Você pode ver o índice do ponteiro que acabou de subir usando getActionIndex().
  • ACTION_UP: enviado quando o último ponteiro sai da tela.
  • ACTION_CANCEL: indica que o gesto inteiro, incluindo todos os ponteiros, foi cancelado.

Gestos de início e término

Um gesto é uma série de eventos que começam com um evento ACTION_DOWN e terminam com um ACTION_UP ou ACTION_CANCEL. Há um gesto ativo por vez. As ações DOWN, MOVE, UP e CANCEL se aplicam a todo o gesto. Por exemplo, um evento com ACTION_MOVE pode indicar um movimento para todos os ponteiros para baixo nesse momento.

Acompanhar os cursores

Use o índice e o ID do ponteiro para rastrear as posições individuais dos ponteiros em um MotionEvent.

  • Índice: um MotionEvent armazena informações de ponteiro em uma matriz. O índice de um ponteiro é a posição dele dentro dessa matriz. A maioria dos métodos MotionEvent usa o índice do ponteiro como parâmetro, em vez do ID dele.
  • ID: cada ponteiro também tem um mapeamento de ID que permanece persistente entre eventos de toque para rastrear um ponteiro individual em todo o gesto.

Ponteiros individuais aparecem dentro de um evento de movimento em uma ordem indefinida. Assim, o índice de um ponteiro pode mudar de um evento para o próximo, mas o ID do ponteiro de um ponteiro vai permanecer constante enquanto o ponteiro permanecer ativo. Use o método getPointerId() para receber o ID de um ponteiro e rastreá-lo em todos os eventos de movimento subsequentes em um gesto. Em seguida, para eventos de movimento sucessivos, use o método findPointerIndex() para acessar o índice do ponteiro de um determinado ID nesse evento de movimento. Por exemplo:

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

Para oferecer suporte a vários ponteiros de toque, você pode armazenar todos os ponteiros ativos em cache com os IDs no horário dos eventos ACTION_POINTER_DOWN e ACTION_DOWN individuais. Remova os ponteiros do cache nos eventos ACTION_POINTER_UP e ACTION_UP. Esses IDs em cache podem ser úteis para processar outros eventos de ação corretamente. Por exemplo, ao processar um evento ACTION_MOVE, encontre o índice de cada ID de ponteiro ativo armazenado em cache, recupere as coordenadas do ponteiro usando as funções getX() e getY() e compare essas coordenadas com as coordenadas em cache para descobrir quais ponteiros foram movidos.

Use a função getActionIndex() apenas com eventos ACTION_POINTER_UP e ACTION_POINTER_DOWN. Não use essa função com eventos ACTION_MOVE, porque ela sempre retorna 0.

Recuperar ações MotionEvent

Use o método getActionMasked() ou a versão de compatibilidade MotionEventCompat.getActionMasked() para recuperar a ação de um MotionEvent. Ao contrário do método getAction() anterior, getActionMasked() foi projetado para funcionar com vários ponteiros. Retorna a ação sem os índices de ponteiro. Para ações com um índice de ponteiro válido, use getActionIndex() para retornar o índice dos ponteiros associados à ação, conforme mostrado no snippet abaixo.

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 "";
}
Figura 1. Padrões de desenho multitoque.

Outros recursos

Para ver mais informações relacionadas a eventos de entrada, consulte as seguintes referências: