Gerenciar gestos com vários toques

Um gesto com vários toques acontece quando diversos ponteiros (dedos) tocam na tela ao mesmo tempo. Esta lição descreve como detectar gestos que envolvem vários ponteiros.

Confira os seguintes recursos relacionados:

Rastrear vários ponteiros

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

  • ACTION_DOWN: para o primeiro ponteiro que toca a tela. Isso inicia o gesto. Os dados desse ponteiro estão sempre no índice 0 do MotionEvent.
  • ACTION_POINTER_DOWN: para ponteiros extras que entram na tela além do primeiro. Os dados desse ponteiro estão no índice retornado por getActionIndex().
  • ACTION_MOVE: uma mudança ocorreu durante um gesto de pressionar.
  • ACTION_POINTER_UP: enviado quando um ponteiro não primário sobe.
  • ACTION_UP: enviado quando o último ponteiro deixa a tela.

Rastreie os ponteiros individuais em um MotionEvent por meio do índice e do código de cada ponteiro:

  • Índice: um MotionEvent armazena com eficiência informações sobre cada ponteiro em uma matriz. O índice de um ponteiro é a posição dele dentro dessa matriz. A maioria dos métodos MotionEvent que você usa para interagir com ponteiros usa o índice do ponteiro como parâmetro, não o código dele.
  • Código: cada ponteiro também tem um mapeamento de código que permanece nos eventos de toque para rastrear um ponteiro individual em todo o gesto.

A ordem em que os ponteiros individuais aparecem em um evento de movimento é indefinida. Assim, o índice de um ponteiro pode mudar de um evento para o próximo, mas o código do ponteiro permanecerá constante enquanto o ponteiro permanecer ativo. Use o método getPointerId() para ver o código 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 ver o índice do ponteiro de um determinado código 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);
        ...
    }
    

Conseguir a ação de um MotionEvent

Use sempre o método getActionMasked() (ou melhor, a versão de compatibilidade MotionEventCompat.getActionMasked()) para recuperar a ação de um MotionEvent. Diferente do método getAction() mais antigo, getActionMasked() foi desenvolvido para funcionar com vários ponteiros. Ele retorna a ação mascarada que está sendo realizada sem incluir os bits do índice do ponteiro. Você pode usar getActionIndex() para retornar o índice do ponteiro associado à ação. Isso é ilustrado no snippet abaixo.

Observação: este exemplo usa a classe MotionEventCompat. Essa classe está na Biblioteca de Suporte. Use MotionEventCompat para oferecer a melhor compatibilidade para uma ampla variedade de plataformas. Veja que o MotionEventCompat não substitui a classe MotionEvent. Em vez disso, ele oferece métodos utilitários estáticos para os quais você passa o objeto MotionEvent a fim de receber a ação visada associada a esse 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 mais discussões sobre o uso de vários toques e para ver alguns exemplos, consulte a lição Arrastar e dimensionar.