Detectar gestos comuns

Um "gesto de toque" acontece quando um usuário coloca um ou mais dedos na tela, e seu aplicativo interpreta esse padrão de toques como um gesto específico. Existem duas fases correspondentes para a detecção de gestos:

  1. Coletar dados sobre eventos de toque.
  2. Interpretar os dados para verificar se eles atendem aos critérios de algum dos gestos com que o app é compatível.

Confira os seguintes recursos relacionados:

Classes da Biblioteca de Suporte

Os exemplos desta lição usam as classes GestureDetectorCompat e MotionEventCompat. Essas classes estão na Biblioteca de Suporte. Use as classes da Biblioteca de Suporte sempre que possível para oferecer compatibilidade com dispositivos com o Android 1.6 e mais recente. Observe que MotionEventCompat não é um substituto para a classe MotionEvent. Em vez disso, ele oferece métodos utilitários estáticos para os quais você transmite o objeto MotionEvent a fim de receber a ação desejada associada a esse evento.

Coletar dados

Quando um usuário coloca um ou mais dedos na tela, aciona o callback onTouchEvent() na visualização que recebeu os eventos de toque. Para cada sequência de eventos de toque (posição, pressão, tamanho, adição de outro dedo etc.) que é identificada como um gesto, onTouchEvent() é acionado várias vezes.

O gesto começa quando o usuário toca na tela pela primeira vez, continua enquanto o sistema rastreia a posição dos dedos do usuário e termina com a captura do evento final dos dedos do usuário deixando a tela. Durante essa interação, o MotionEvent exibido em onTouchEvent() fornece os detalhes de cada interação. Seu app pode usar os dados fornecidos pelo MotionEvent para determinar se um gesto é relevante para ele.

Capturar eventos de toque para uma atividade ou visualização

Para interceptar eventos de toque em uma atividade ou visualização, modifique o callback onTouchEvent().

O snippet a seguir usa getActionMasked() para extrair a ação que o usuário executou do parâmetro event. Essa ação disponibiliza os dados brutos necessários para determinar se um gesto relevante ocorreu:

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

Você pode fazer o próprio processamento nesses eventos para determinar se ocorreu um gesto. Esse é o tipo de processamento necessário para um gesto personalizado. No entanto, se o app usa gestos comuns, como tocar duas vezes, tocar e manter pressionado, movimentar etc., você pode se beneficiar com a classe GestureDetector. GestureDetector facilita a detecção de gestos comuns sem que você precise processar os eventos de toque individuais por conta própria. Esse assunto é discutido abaixo em Detectar gestos.

Capturar eventos de toque para uma visualização única

Como alternativa a onTouchEvent(), você pode anexar um objeto View.OnTouchListener a qualquer objeto View usando o método setOnTouchListener(). Dessa forma, é possível ouvir eventos de toque sem subclassificar uma View existente. Por exemplo:

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

Cuidado ao criar um listener que retorne false para o evento ACTION_DOWN. Se você fizer isso, o listener não será chamado para a string de eventos ACTION_MOVE e ACTION_UP subsequente. Isso ocorre porque ACTION_DOWN é o ponto de partida para todos os eventos de toque.

Se você estiver criando uma visualização personalizada, poderá modificar onTouchEvent(), conforme descrito acima.

Detectar gestos

O Android oferece a classe GestureDetector para a detecção de gestos comuns. Alguns dos gestos compatíveis incluem onDown(), onLongPress(), onFling() entre outros. Você pode usar GestureDetector em conjunto com o método onTouchEvent() descrito acima.

Detectar todos os gestos compatíveis

Quando você instancia um objeto GestureDetectorCompat, um dos parâmetros necessários é uma classe que implementa a interface GestureDetector.OnGestureListener. GestureDetector.OnGestureListener notifica os usuários quando ocorre um evento de toque específico. Para que seu objeto GestureDetector receba eventos, modifique o método onTouchEvent() da visualização ou da atividade e transmita todos os eventos observados para a instância do detector.

No snippet a seguir, um valor de retorno de true dos métodos on<TouchEvent> individuais indica que você processou o evento de toque. Um valor de retorno de false transmite os eventos pela pilha de visualização até que o toque seja processado.

Execute o snippet a seguir para ter uma ideia de como as ações são iniciadas quando você interage com a touchscreen e qual é o conteúdo do MotionEvent para cada evento de toque. Você perceberá que uma grande quantidade de dados é gerada mesmo para as interações 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;
        }
    }
    

Detectar um subconjunto de gestos compatíveis

Se você quer processar apenas alguns gestos, pode estender o GestureDetector.SimpleOnGestureListener em vez de implementar a interface do GestureDetector.OnGestureListener.

GestureDetector.SimpleOnGestureListener fornece uma implementação para todos os métodos on<TouchEvent> retornando false para todos eles. Assim, você pode modificar apenas os métodos do seu interesse. Por exemplo, o snippet abaixo cria uma classe que estende GestureDetector.SimpleOnGestureListener e modifica onFling() e onDown().

Quer você use ou não o GestureDetector.OnGestureListener, implementar um método onDown() que retorna true é uma prática recomendada. Isso ocorre porque todos os gestos começam com uma mensagem onDown(). Se você retornar false de onDown(), como GestureDetector.SimpleOnGestureListener faz por padrão, o sistema presume que você quer ignorar o restante do gesto e os outros métodos de GestureDetector.OnGestureListener nunca serão chamados. Isso pode causar problemas inesperados no seu app. A única vez que é necessário retornar false de onDown() é se você realmente quiser ignorar um gesto inteiro.

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