Acompanhar movimentos de toque e do cursor

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.

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, a pressão ou o tamanho do contato de toque atual mudar. Conforme descrito em Detectar gestos comuns, todos esses eventos são registrados no parâmetro MotionEvent de onTouchEvent().

Como o toque baseado no 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 contato simples. Para ajudar os apps a distinguir entre gestos baseados em movimento (como deslizar) e gestos sem movimento (como um único toque), o Android inclui a noção de tolerância de toque. A tolerância de toque se refere à distância em pixels que o toque do usuário pode percorrer antes que o gesto seja interpretado como um gesto de movimento. Para mais informações sobre esse tópico, consulte Gerenciar eventos de toque em um ViewGroup.

Há várias maneiras de rastrear o movimento em um gesto, dependendo das necessidades do aplicativo. Confira alguns exemplos:

  • As posições inicial e final de um ponteiro, como ao mover um objeto na tela do ponto A para o ponto B.
  • A direção em que o ponteiro está se deslocando, conforme determinado pelas coordenadas X e Y.
  • O histórico. É possível descobrir o tamanho do histórico de um gesto chamando o método MotionEvent getHistorySize(). É possível saber as posições, os tamanhos, o horário e a pressão de cada um dos eventos históricos usando os métodos getHistorical<Value> do evento de movimento. O histórico é útil ao renderizar um rastro do dedo do usuário, por exemplo, para desenhar por toque. Consulte a referência MotionEvent para mais detalhes.
  • A velocidade do ponteiro à medida que ele se move na tela touchscreen.

Confira estes recursos relacionados:

Velocidade de rastreamento

Você pode ter um gesto de movimento com base na distância ou direção que o ponteiro percorre. No entanto, a velocidade geralmente é um fator determinante para rastrear as características de um gesto ou decidir se ele ocorreu. Para facilitar o cálculo da velocidade, o Android oferece a classe VelocityTracker. VelocityTracker ajuda a rastrear a velocidade dos eventos de toque. Isso é útil para gestos em que a velocidade faz parte dos critérios do gesto, como uma rolagem rápida.

Este exemplo ilustra a finalidade 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. It's 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. It's 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 e clientes de área de trabalho remota e virtualização, se beneficiam do controle sobre o ponteiro do mouse. A captura de ponteiro é um recurso disponível no Android 8.0 (nível 26 da API) e versões mais recentes que oferece esse controle enviando todos os eventos do mouse para uma visualização focada no seu app.

Solicitar captura de ponteiro

Uma visualização no seu app só pode solicitar a captura de ponteiro quando a hierarquia de visualização que a contém está em foco. Por esse motivo, 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 concluída, o Android chamará onPointerCaptureChange(true). O sistema envia os eventos de mouse para a visualização focada no app, desde que ela esteja na mesma hierarquia da que solicitou a captura. Outros apps param de receber eventos de mouse até que a captura seja liberada, incluindo eventos ACTION_OUTSIDE. O Android envia normalmente eventos de ponteiro de outras origens, mas o ponteiro do mouse não fica mais visível.

Processar eventos de ponteiro capturados

Depois que uma visualização adquire a captura de ponteiro, o Android envia os eventos do mouse. A visualização focada pode processar os eventos realizando uma das seguintes tarefas:

O exemplo de código abaixo 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 is
    // 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 is
  // successfully processed.
  return true;
}

O exemplo de código abaixo 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 is
    // 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 is
    // successfully processed.
    return true;
  }
});

Quer você use uma visualização personalizada ou registre um listener, sua visualização recebe um MotionEvent com coordenadas de ponteiro que especificam movimentos relativos, como deltas ou Y, semelhantes às coordenadas enviadas por um dispositivo trackball. É possível extrair as coordenadas usando getX() e getY().

Liberar captura de ponteiro

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

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() explicitamente, geralmente porque a hierarquia de visualização que contém a visualização que solicita a captura perde o foco.