Cómo detectar gestos comunes

Un "gesto táctil" se produce cuando un usuario coloca uno o más dedos en la pantalla táctil y tu aplicación interpreta ese patrón de toques como un gesto específico. La detección de gestos consta de dos partes:

  1. Recopilación de datos sobre eventos táctiles
  2. Interpretación de los datos para ver si cumplen con los criterios de cualquiera de los gestos que admite tu app

Consulta los siguientes recursos relacionados:

Clases de bibliotecas de compatibilidad

En los ejemplos de esta lección, se usan las clases GestureDetectorCompat y MotionEventCompat, que se encuentran en la Biblioteca de compatibilidad. Deberías usar las clases de la Biblioteca de compatibilidad siempre que sea posible a fin de proporcionar compatibilidad con dispositivos que ejecutan Android 1.6 y versiones posteriores. Ten en cuenta que MotionEventCompat no es un reemplazo de la clase MotionEvent. En cambio, proporciona métodos de utilidades estáticos que puedes pasar al objeto MotionEvent para recibir la acción deseada asociada con ese evento.

Recopilación de datos

Cuando un usuario coloca uno o más dedos en la pantalla, se activa la devolución de llamada onTouchEvent() en el objeto View que recibió los eventos táctiles. Por cada secuencia de eventos táctiles (posición, presión, tamaño, adición de otro dedo, etc.) que se identifica como gesto, onTouchEvent() se activa varias veces.

El gesto comienza cuando el usuario toca la pantalla por primera vez, continúa mientras el sistema rastrea la posición de los dedos del usuario y termina cuando captura el evento final de los dedos del usuario al salir de la pantalla. Durante esa interacción, el objeto MotionEvent enviado a onTouchEvent() proporciona los detalles de cada interacción. Tu app puede usar los datos que proporciona MotionEvent para determinar si se produjo algún gesto importante.

Cómo capturar eventos táctiles para objetos Activity o View

Para interceptar eventos táctiles en un objeto Activity o View, anula la devolución de llamada onTouchEvent().

En el siguiente fragmento, se usa getActionMasked() para extraer la acción que realizó el usuario del parámetro event. Esto te proporciona los datos sin procesar que necesitas para determinar si se produjo un gesto que te interesa:

Kotlin

    class MainActivity : Activity() {
        ...
        // This example shows an Activity, but you would use the same approach if
        // you were subclassing a View.
        override fun onTouchEvent(event: MotionEvent): Boolean {

            val action: Int = MotionEventCompat.getActionMasked(event)

            return when (action) {
                MotionEvent.ACTION_DOWN -> {
                    Log.d(DEBUG_TAG, "Action was DOWN")
                    true
                }
                MotionEvent.ACTION_MOVE -> {
                    Log.d(DEBUG_TAG, "Action was MOVE")
                    true
                }
                MotionEvent.ACTION_UP -> {
                    Log.d(DEBUG_TAG, "Action was UP")
                    true
                }
                MotionEvent.ACTION_CANCEL -> {
                    Log.d(DEBUG_TAG, "Action was CANCEL")
                    true
                }
                MotionEvent.ACTION_OUTSIDE -> {
                    Log.d(DEBUG_TAG, "Movement occurred outside bounds of current screen element")
                    true
                }
                else -> super.onTouchEvent(event)
            }
        }
    }
    

Java

    public class MainActivity extends Activity {
    ...
    // This example shows an Activity, but you would use the same approach if
    // you were subclassing a View.
    @Override
    public boolean onTouchEvent(MotionEvent event){

        int action = MotionEventCompat.getActionMasked(event);

        switch(action) {
            case (MotionEvent.ACTION_DOWN) :
                Log.d(DEBUG_TAG,"Action was DOWN");
                return true;
            case (MotionEvent.ACTION_MOVE) :
                Log.d(DEBUG_TAG,"Action was MOVE");
                return true;
            case (MotionEvent.ACTION_UP) :
                Log.d(DEBUG_TAG,"Action was UP");
                return true;
            case (MotionEvent.ACTION_CANCEL) :
                Log.d(DEBUG_TAG,"Action was CANCEL");
                return true;
            case (MotionEvent.ACTION_OUTSIDE) :
                Log.d(DEBUG_TAG,"Movement occurred outside bounds " +
                        "of current screen element");
                return true;
            default :
                return super.onTouchEvent(event);
        }
    }
    

Luego, puedes hacer tu propio procesamiento en estos eventos para determinar si se produjo un gesto. Este es el tipo de procesamiento que tendrías que hacer para un gesto personalizado. Sin embargo, si tu app utiliza gestos comunes (como presionar dos veces, mantener presionado, arrastrar y soltar, etc.), puedes aprovechar la clase GestureDetector. GestureDetector facilita la detección de gestos comunes sin procesar los eventos táctiles individuales. Este tema se analiza a continuación en Cómo detectar gestos.

Cómo capturar eventos táctiles para un solo evento View

Como alternativa a onTouchEvent(), puedes adjuntar un objeto View.OnTouchListener a cualquier objeto View con el método setOnTouchListener(). Esto permite escuchar eventos táctiles sin subclasificar un objeto View existente. Por ejemplo:

Kotlin

    findViewById<View>(R.id.my_view).setOnTouchListener { v, event ->
        // ... Respond to touch events
        true
    }
    

Java

    View myView = findViewById(R.id.my_view);
    myView.setOnTouchListener(new OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {
            // ... Respond to touch events
            return true;
        }
    });
    

Ten cuidado de no crear un objeto de escucha que muestre false para el evento ACTION_DOWN. De lo contrario, no se llamará al objeto de escucha para la sucesiva serie de eventos ACTION_MOVE y ACTION_UP. Esto se debe a que ACTION_DOWN es el punto de partida para todos los eventos táctiles.

Si estás creando un objeto View personalizado, puedes anular onTouchEvent(), como se describe más arriba.

Cómo detectar gestos

Android proporciona la clase GestureDetector para detectar gestos comunes. Entre los gestos que se admiten, se incluyen onDown(), onLongPress(), onFling(), etc. Puedes usar GestureDetector junto con el método onTouchEvent() descrito anteriormente.

Cómo detectar todos los gestos compatibles

Cuando creas una instancia de un objeto GestureDetectorCompat, uno de los parámetros que toma es una clase que implementa la interfaz GestureDetector.OnGestureListener. GestureDetector.OnGestureListener notifica a los usuarios cuando se produce un evento táctil en particular. Para que el objeto GestureDetector pueda recibir eventos, anula el método onTouchEvent() de View o Activity, y pasa todos los eventos observados a la instancia del detector.

En el siguiente fragmento, un valor de resultado true de los métodos on<TouchEvent> indica que se controló el evento táctil. Un valor de resultado false pasa eventos a través de la pila de vistas hasta que el evento táctil se haya controlado con éxito.

Ejecuta el siguiente fragmento para tener una idea de cómo se desencadenan las acciones cuando interactúas con la pantalla táctil y cuáles son los contenidos de MotionEvent para cada evento táctil. Te darás cuenta de cuántos datos se generan incluso para interacciones simples.

Kotlin

    private const val DEBUG_TAG = "Gestures"

    class MainActivity :
            Activity(),
            GestureDetector.OnGestureListener,
            GestureDetector.OnDoubleTapListener {

        private lateinit var mDetector: GestureDetectorCompat

        // Called when the activity is first created.
        public override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            // Instantiate the gesture detector with the
            // application context and an implementation of
            // GestureDetector.OnGestureListener
            mDetector = GestureDetectorCompat(this, this)
            // Set the gesture detector as the double tap
            // listener.
            mDetector.setOnDoubleTapListener(this)
        }

        override fun onTouchEvent(event: MotionEvent): Boolean {
            return if (mDetector.onTouchEvent(event)) {
                true
            } else {
                super.onTouchEvent(event)
            }
        }

        override fun onDown(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDown: $event")
            return true
        }

        override fun onFling(
                event1: MotionEvent,
                event2: MotionEvent,
                velocityX: Float,
                velocityY: Float
        ): Boolean {
            Log.d(DEBUG_TAG, "onFling: $event1 $event2")
            return true
        }

        override fun onLongPress(event: MotionEvent) {
            Log.d(DEBUG_TAG, "onLongPress: $event")
        }

        override fun onScroll(
                event1: MotionEvent,
                event2: MotionEvent,
                distanceX: Float,
                distanceY: Float
        ): Boolean {
            Log.d(DEBUG_TAG, "onScroll: $event1 $event2")
            return true
        }

        override fun onShowPress(event: MotionEvent) {
            Log.d(DEBUG_TAG, "onShowPress: $event")
        }

        override fun onSingleTapUp(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onSingleTapUp: $event")
            return true
        }

        override fun onDoubleTap(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDoubleTap: $event")
            return true
        }

        override fun onDoubleTapEvent(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDoubleTapEvent: $event")
            return true
        }

        override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onSingleTapConfirmed: $event")
            return true
        }

    }
    

Java

    public class MainActivity extends Activity implements
            GestureDetector.OnGestureListener,
            GestureDetector.OnDoubleTapListener{

        private static final String DEBUG_TAG = "Gestures";
        private GestureDetectorCompat mDetector;

        // Called when the activity is first created.
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // Instantiate the gesture detector with the
            // application context and an implementation of
            // GestureDetector.OnGestureListener
            mDetector = new GestureDetectorCompat(this,this);
            // Set the gesture detector as the double tap
            // listener.
            mDetector.setOnDoubleTapListener(this);
        }

        @Override
        public boolean onTouchEvent(MotionEvent event){
            if (this.mDetector.onTouchEvent(event)) {
                return true;
            }
            return super.onTouchEvent(event);
        }

        @Override
        public boolean onDown(MotionEvent event) {
            Log.d(DEBUG_TAG,"onDown: " + event.toString());
            return true;
        }

        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2,
                float velocityX, float velocityY) {
            Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
            return true;
        }

        @Override
        public void onLongPress(MotionEvent event) {
            Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
        }

        @Override
        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
                float distanceY) {
            Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString());
            return true;
        }

        @Override
        public void onShowPress(MotionEvent event) {
            Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
        }

        @Override
        public boolean onSingleTapUp(MotionEvent event) {
            Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent event) {
            Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
            return true;
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent event) {
            Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent event) {
            Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
            return true;
        }
    }
    

Cómo detectar un subconjunto de gestos compatibles

Si solo quieres procesar algunos gestos, puedes extender GestureDetector.SimpleOnGestureListener en lugar de implementar la interfaz GestureDetector.OnGestureListener.

GestureDetector.SimpleOnGestureListener proporciona una implementación para todos los métodos on<TouchEvent> al mostrar el valor false para todos ellos. Por lo tanto, puedes anular solo los métodos que te interesan. Por ejemplo, el siguiente fragmento crea una clase que extiende GestureDetector.SimpleOnGestureListener y anula onFling() y onDown().

Ya sea que uses GestureDetector.OnGestureListener o no, se recomienda implementar un método onDown() que muestre true. Esto se debe a que todos los gestos comienzan con un mensaje onDown(). Si muestras false desde onDown(), como lo hace GestureDetector.SimpleOnGestureListener de manera predeterminada, el sistema asume que quieres ignorar el resto del gesto y nunca se llama a los otros métodos de GestureDetector.OnGestureListener. Esta acción puede causar problemas inesperados en tu app. En el único momento en el que deberías mostrar false desde onDown() es si realmente deseas ignorar un gesto completo.

Kotlin

    private const val DEBUG_TAG = "Gestures"

    class MainActivity : Activity() {

        private lateinit var mDetector: GestureDetectorCompat

        public override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            mDetector = GestureDetectorCompat(this, MyGestureListener())
        }

        override fun onTouchEvent(event: MotionEvent): Boolean {
            mDetector.onTouchEvent(event)
            return super.onTouchEvent(event)
        }

        private class MyGestureListener : GestureDetector.SimpleOnGestureListener() {

            override fun onDown(event: MotionEvent): Boolean {
                Log.d(DEBUG_TAG, "onDown: $event")
                return true
            }

            override fun onFling(
                    event1: MotionEvent,
                    event2: MotionEvent,
                    velocityX: Float,
                    velocityY: Float
            ): Boolean {
                Log.d(DEBUG_TAG, "onFling: $event1 $event2")
                return true
            }
        }
    }
    

Java

    public class MainActivity extends Activity {

        private GestureDetectorCompat mDetector;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mDetector = new GestureDetectorCompat(this, new MyGestureListener());
        }

        @Override
        public boolean onTouchEvent(MotionEvent event){
            this.mDetector.onTouchEvent(event);
            return super.onTouchEvent(event);
        }

        class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
            private static final String DEBUG_TAG = "Gestures";

            @Override
            public boolean onDown(MotionEvent event) {
                Log.d(DEBUG_TAG,"onDown: " + event.toString());
                return true;
            }

            @Override
            public boolean onFling(MotionEvent event1, MotionEvent event2,
                    float velocityX, float velocityY) {
                Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
                return true;
            }
        }
    }