The Android Developer Challenge is back! Submit your idea before December 2.

Rastrear movimentos de toque e ponteiro

Esta lição descreve como rastrear o movimento em eventos de toque.

Um novo onTouchEvent() é acionado com um evento ACTION_MOVE sempre que a posição, pressão ou tamanho do contato de toque atual muda. Conforme descrito em Detectar gestos comuns, todos estes eventos são registrados no parâmetro MotionEvent de onTouchEvent().

Como o toque com o dedo nem sempre é a forma mais precisa de interação, a detecção de eventos de toque geralmente se baseia mais no movimento do que no simples contato. Para ajudar os apps a diferenciar gestos de movimento (como deslizar) de gestos sem movimento (como um único toque), o Android inclui a noção de "tolerância de toque". A tolerância de toque refere-se à distância em pixels que o toque do usuário pode percorrer antes que o gesto seja interpretado como um gesto de movimento. Para ver mais discussões sobre esse assunto, consulte Gerenciar eventos de toque em um ViewGroup.

Existem várias maneiras de rastrear o movimento em um gesto, dependendo das necessidades do seu aplicativo. Por exemplo:

  • As posições inicial e final de um ponteiro (por exemplo, mova um objeto na tela do ponto A para o ponto B).
  • A direção do percurso do ponteiro, conforme determinado pelas coordenadas x e y.
  • O histórico. Você pode encontrar o tamanho do histórico de um gesto chamando o método MotionEvent getHistorySize(). É possível saber as posições, tamanhos, horários e pressões de cada evento histórico usando os métodos getHistorical<Value> do evento de movimento. O histórico é útil ao renderizar uma trilha do dedo do usuário, bem como para desenhar com o toque. Consulte a referência do MotionEvent para ver mais detalhes.
  • A velocidade do ponteiro conforme ele se move na tela.

Confira os seguintes recursos relacionados:

Velocidade de rastreamento

Você pode ter um gesto de movimento baseado apenas na distância e/ou direção que o ponteiro percorre. No entanto, a velocidade geralmente é um fator determinante para o rastreamento das características de um gesto ou até para identificar se o gesto realmente ocorreu. Para facilitar o cálculo da velocidade, o Android oferece a classe VelocityTracker. A VelocityTracker ajuda a rastrear a velocidade dos eventos de toque. Isso é útil quando a velocidade faz parte dos critérios do gesto, como no caso de uma rolagem rápida.

Este é um exemplo simples que ilustra o objetivo dos métodos na API VelocityTracker:

Kotlin

    private const val DEBUG_TAG = "Velocity"

    class MainActivity : Activity() {
        private var mVelocityTracker: VelocityTracker? = null

        override fun onTouchEvent(event: MotionEvent): Boolean {

            when (event.actionMasked) {
                MotionEvent.ACTION_DOWN -> {
                    // Reset the velocity tracker back to its initial state.
                    mVelocityTracker?.clear()
                    // If necessary retrieve a new VelocityTracker object to watch the
                    // velocity of a motion.
                    mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
                    // Add a user's movement to the tracker.
                    mVelocityTracker?.addMovement(event)
                }
                MotionEvent.ACTION_MOVE -> {
                    mVelocityTracker?.apply {
                        val pointerId: Int = event.getPointerId(event.actionIndex)
                        addMovement(event)
                        // When you want to determine the velocity, call
                        // computeCurrentVelocity(). Then call getXVelocity()
                        // and getYVelocity() to retrieve the velocity for each pointer ID.
                        computeCurrentVelocity(1000)
                        // Log velocity of pixels per second
                        // Best practice to use VelocityTrackerCompat where possible.
                        Log.d("", "X velocity: ${getXVelocity(pointerId)}")
                        Log.d("", "Y velocity: ${getYVelocity(pointerId)}")
                    }
                }
                MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                    // Return a VelocityTracker object back to be re-used by others.
                    mVelocityTracker?.recycle()
                    mVelocityTracker = null
                }
            }
            return true
        }
    }
    

Java

    public class MainActivity extends Activity {
        private static final String DEBUG_TAG = "Velocity";
            ...
        private VelocityTracker mVelocityTracker = null;
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            int index = event.getActionIndex();
            int action = event.getActionMasked();
            int pointerId = event.getPointerId(index);

            switch(action) {
                case MotionEvent.ACTION_DOWN:
                    if(mVelocityTracker == null) {
                        // Retrieve a new VelocityTracker object to watch the
                        // velocity of a motion.
                        mVelocityTracker = VelocityTracker.obtain();
                    }
                    else {
                        // Reset the velocity tracker back to its initial state.
                        mVelocityTracker.clear();
                    }
                    // Add a user's movement to the tracker.
                    mVelocityTracker.addMovement(event);
                    break;
                case MotionEvent.ACTION_MOVE:
                    mVelocityTracker.addMovement(event);
                    // When you want to determine the velocity, call
                    // computeCurrentVelocity(). Then call getXVelocity()
                    // and getYVelocity() to retrieve the velocity for each pointer ID.
                    mVelocityTracker.computeCurrentVelocity(1000);
                    // Log velocity of pixels per second
                    // Best practice to use VelocityTrackerCompat where possible.
                    Log.d("", "X velocity: " + mVelocityTracker.getXVelocity(pointerId));
                    Log.d("", "Y velocity: " + mVelocityTracker.getYVelocity(pointerId));
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    // Return a VelocityTracker object back to be re-used by others.
                    mVelocityTracker.recycle();
                    break;
            }
            return true;
        }
    }
    

Usar captura de ponteiro

Alguns apps, como jogos, áreas de trabalho remotas e clientes de virtualização, são significativamente beneficiados com o controle do ponteiro do mouse. A captura de ponteiro é um recurso disponível no Android 8.0 (API nível 26) e posterior que proporciona esse controle enviando todos os eventos de mouse para uma visualização focalizada no seu app.

Solicitar captura de ponteiro

Uma visualização no seu app só pode solicitar a captura de ponteiro quando a hierarquia da visualização que a contém está em foco. Por isso, solicite a captura de ponteiro quando houver uma ação específica do usuário na visualização, como durante um evento onClick() ou no manipulador de eventos onWindowFocusChanged() da sua atividade.

Para solicitar a captura de ponteiro, chame o método requestPointerCapture() na visualização. O exemplo de código a seguir mostra como solicitar a captura de ponteiro quando o usuário clica em uma visualização:

Kotlin

    fun onClick(view: View) {
        view.requestPointerCapture()
    }
    

Java

    @Override
    public void onClick(View view) {
        view.requestPointerCapture();
    }
    

Depois que a solicitação para capturar o ponteiro for realizada, o Android chamará onPointerCaptureChange(true). O sistema enviará os eventos de mouse para a visualização focalizada no seu app, desde que ela esteja na mesma hierarquia da visualização que solicitou a captura. Outros apps deixarão de receber eventos de mouse até a captura ser liberada, incluindo os eventos ACTION_OUTSIDE. O Android enviará eventos de ponteiro de outras fontes além do mouse normalmente, mas o ponteiro do mouse não estará mais visível.

Processar eventos de ponteiro capturados

Depois que uma visualização recebe a captura do ponteiro, o Android começa a enviar os eventos de mouse. Sua visualização focalizada pode processar os eventos realizando uma das tarefas a seguir:

  1. Se você está usando uma visualização personalizada, modifique onCapturedPointerEvent(MotionEvent).
  2. Caso contrário, registre um OnCapturedPointerListener.

O exemplo de código a seguir mostra como implementar onCapturedPointerEvent(MotionEvent):

Kotlin

    override fun onCapturedPointerEvent(motionEvent: MotionEvent): Boolean {
        // Get the coordinates required by your app
        val verticalOffset: Float = motionEvent.y
        // Use the coordinates to update your view and return true if the event was
        // successfully processed
        return true
    }
    

Java

    @Override
    public boolean onCapturedPointerEvent(MotionEvent motionEvent) {
      // Get the coordinates required by your app
      float verticalOffset = motionEvent.getY();
      // Use the coordinates to update your view and return true if the event was
      // successfully processed
      return true;
    }
    

O exemplo de código a seguir mostra como registrar um OnCapturedPointerListener:

Kotlin

    myView.setOnCapturedPointerListener { view, motionEvent ->
        // Get the coordinates required by your app
        val horizontalOffset: Float = motionEvent.x
        // Use the coordinates to update your view and return true if the event was
        // successfully processed
        true
    }
    

Java

    myView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() {
      @Override
      public boolean onCapturedPointer (View view, MotionEvent motionEvent) {
        // Get the coordinates required by your app
        float horizontalOffset = motionEvent.getX();
        // Use the coordinates to update your view and return true if the event was
        // successfully processed
        return true;
      }
    });
    

Independentemente de você usar uma visualização personalizada ou registrar um listener, sua visualização recebe um MotionEvent com coordenadas de ponteiro que especificam movimentos relativos, como deltas X/Y, semelhantes às coordenadas enviadas por um dispositivo trackball. Você pode recuperar as coordenadas usando getX() e getY().

Liberar captura de ponteiro

A visualização no seu app pode liberar a captura de ponteiro chamando releasePointerCapture(), conforme mostrado neste exemplo de código:

Kotlin

    override fun onClick(view: View) {
        view.releasePointerCapture()
    }
    

Java

    @Override
    public void onClick(View view) {
        view.releasePointerCapture();
    }
    

O sistema pode retirar a captura da visualização sem que você chame releasePointerCapture() de maneira explícita. Normalmente, isso acontece porque a hierarquia relacionada à visualização que solicitou a captura perdeu o foco.