Acompanhar movimentos de toque e do cursor

Experimente trabalhar com o Compose
O Jetpack Compose é o kit de ferramentas de interface recomendado para o Android. Saiba como 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 muda. Conforme descrito em Detectar gestos comuns, todos esses eventos são registrados no MotionEvent parâmetro de onTouchEvent().

Como o toque com os dedos 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 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 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. Veja alguns exemplos:

  • A posição 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.
  • Histórico. É possível encontrar o tamanho do histórico de um gesto chamando o MotionEvent método getHistorySize(). Em seguida, é possível acessar as posições, tamanhos, tempo e 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 desenho por toque. Consulte a referência MotionEvent para mais detalhes.
  • A velocidade do ponteiro conforme ele se move na tela sensível ao toque.

Confira estes recursos relacionados:

Velocidade de rastreamento

Você pode ter um gesto de movimento baseado na distância ou na direção que o ponteiro percorre. No entanto, a velocidade geralmente é um fator determinante no rastreamento das características de um gesto ou na decisão de se o gesto ocorreu. Para facilitar o cálculo da velocidade, o Android oferece a VelocityTracker classe. 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 um deslizar rapidamente.

Confira 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 do ponteiro do mouse. A captura de ponteiro é um recurso disponível no Android 8.0 (nível 26 da API) e mais recente 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 de 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 onClick() evento ou no onWindowFocusChanged() manipulador de eventos da sua atividade.

Para solicitar a captura de ponteiro, chame o requestPointerCapture() método 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();
}

Quando 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 de visualização da visualização que solicitou a captura. Outros apps param de receber eventos de mouse até que a captura seja liberada, incluindo ACTION_OUTSIDE eventos. O Android envia eventos de ponteiro de outras fontes além do mouse normalmente, mas o ponteiro do mouse não fica mais visível.

Processar eventos de ponteiro capturados

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

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 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 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 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 recuperar 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 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 de visualização que contém a visualização que solicitou a captura perde o foco.