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

Como o toque do 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 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 refere-se à distância em pixels que o toque do usuário pode percorrer antes que o gesto seja interpretado como baseado em 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 seu aplicativo. Confira alguns exemplos:

  • As posições inicial e final de um ponteiro, como mover 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. Para encontrar o tamanho do histórico de um gesto, chame o método MotionEvent getHistorySize(). É possível saber as posições, os tamanhos, o horário e as pressões 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, como para mostrar por toques. Consulte a referência MotionEvent para mais detalhes.
  • A velocidade do ponteiro conforme ele se move na tela touchscreen.

Confira estes recursos relacionados:

Velocidade de rastreamento

Você pode ter um gesto de movimento baseado 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, como uma rolagem rápida.

Este é um exemplo que 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 app.

Solicitar captura de ponteiro

Uma visualização no 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, por exemplo, 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 abaixo 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 seu app, desde que ela esteja na mesma hierarquia da visualização que solicitou a captura. Outros apps vão deixar 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 recebe a captura de ponteiro, o Android envia os eventos do mouse. A visualização focada pode processar os eventos realizando uma das tarefas abaixo:

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

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 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 de 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.