Cómo hacer un seguimiento de los movimientos de tocar y del puntero

En esta lección, se describe cómo realizar un seguimiento del movimiento en eventos táctiles.

Un nuevo objeto onTouchEvent() se activa con un evento ACTION_MOVE cada vez que cambia la posición, la presión o el tamaño del contacto táctil actual. Como se describe en Cómo detectar gestos comunes, todos estos eventos se registran en el parámetro MotionEvent de onTouchEvent().

Debido a que el toque con el dedo no siempre es la forma más precisa de interacción, la detección de eventos táctiles a menudo se basa más en el movimiento que en el simple contacto. Para ayudar a las apps a distinguir entre los gestos basados en movimientos (como un deslizamiento) y los gestos sin movimiento (como un solo toque), Android incluye la noción de "margen táctil". El margen táctil se refiere a la distancia en píxeles en que el toque de un usuario puede oscilar antes de que se interprete el gesto como un gesto basado en movimiento. Para obtener más información sobre este tema, consulta Cómo administrar eventos táctiles en un ViewGroup.

Existen varias formas de hacer un seguimiento del movimiento de un gesto, según las necesidades de tu aplicación. Por ejemplo:

  • La posición inicial y final de un puntero (por ejemplo, mover un objeto en pantalla del punto A al punto B).
  • La dirección en la que viaja el puntero, según lo determinado por las coordenadas x e y.
  • Historial. Puedes encontrar el tamaño del historial de un gesto llamando al método MotionEvent getHistorySize(). Luego, puedes obtener las posiciones, los tamaños, el tiempo y las presiones de cada uno de los eventos históricos con los métodos getHistorical<Value> del evento de movimiento. El historial es útil cuando se procesa un rastro del dedo del usuario, como en el dibujo táctil. Para obtener más detalles, consulta la referencia MotionEvent.
  • La velocidad del puntero a medida que se mueve por la pantalla táctil.

Consulta los siguientes recursos relacionados:

Cómo hacer un seguimiento de la velocidad

Puedes tener un gesto basado en movimiento según la distancia o dirección que recorrió el puntero. Sin embargo, la velocidad suele ser un factor determinante para hacer un seguimiento de las características de un gesto o incluso para decidir si se produjo el gesto. A fin de facilitar el cálculo de la velocidad, Android proporciona la clase VelocityTracker. VelocityTracker te ayuda a rastrear la velocidad de los eventos táctiles, lo cual resulta útil para gestos en los que la velocidad es parte de los criterios del gesto, como arrastrar y soltar.

El siguiente es un ejemplo simple que ilustra el propósito de los métodos en la API de 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;
        }
    }
    

Cómo usar la captura del puntero

Para algunas apps, como las de juegos, escritorio remoto y clientes de virtualización, el control del puntero del mouse representa un gran beneficio. La captura de puntero es una función disponible en Android 8.0 (API nivel 26) y versiones posteriores que proporciona dicho control al entregar todos los eventos del mouse en una vista seleccionada de tu app.

Cómo solicitar la captura del puntero

Una vista de tu app puede solicitar la captura del puntero solo cuando la jerarquía de vista que la contiene tiene enfoque. Por este motivo, debes solicitar la captura del puntero cuando haya una acción específica del usuario en la vista, como durante un evento onClick() o en el controlador de eventos onWindowFocusChanged() de tu actividad.

Para solicitar la captura del puntero, llama al método requestPointerCapture() de la vista. En el siguiente ejemplo de código, se muestra cómo solicitar la captura del puntero cuando el usuario hace clic en una vista:

Kotlin

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

Java

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

Una vez que se realiza correctamente la solicitud para capturar el puntero, Android llama a onPointerCaptureChange(true). El sistema envía los eventos del mouse a la vista seleccionada de tu app siempre que esté en la misma jerarquía de vistas que la vista que solicitó la captura. Otras apps dejan de recibir eventos del mouse hasta que se libera la captura, incluidos los eventos ACTION_OUTSIDE. Android envía los eventos del puntero de fuentes distintas al mouse normalmente, pero el puntero del mouse ya no es visible.

Cómo controlar eventos del puntero capturados

Cuando una vista haya adquirido correctamente la captura del puntero, Android comenzará a publicar los eventos del mouse. Tu vista seleccionada puede controlar los eventos mediante una de las siguientes tareas:

  1. Si usas una vista personalizada, anula onCapturedPointerEvent(MotionEvent).
  2. De lo contrario, registra un elemento OnCapturedPointerListener.

En el siguiente ejemplo de código, se muestra cómo 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;
    }
    

En el siguiente ejemplo de código, se muestra cómo registrar un 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;
      }
    });
    

Ya sea que uses una vista personalizada o que registres un objeto de escucha, tu vista recibe un objeto MotionEvent con coordenadas de puntero que especifican movimientos relativos, como deltas X/Y, similares a las coordenadas que envía un dispositivo de bola de seguimiento. Puedes recuperar las coordenadas con getX() y getY().

Cómo liberar la captura del puntero

La vista de tu app puede liberar la captura del puntero llamando a releasePointerCapture(), como se muestra en el siguiente ejemplo de código:

Kotlin

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

Java

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

El sistema puede quitar la captura de la vista sin llamar explícitamente a releasePointerCapture(), por lo general, porque la jerarquía que contiene la vista que solicitó la captura perdió el foco.