Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

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 posterior. É importante observar que MotionEventCompat não substitui a classe MotionEvent. Em vez disso, ele oferece métodos de utilitário estáticos para os quais você passa o objeto MotionEvent a fim de receber a ação visada associada a esse evento.

Coletar dados

Quando um usuário coloca um ou mais dedos na tela, isso aciona o callback onTouchEvent() na View 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.) finalmente 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 toda essa interação, o MotionEvent entregue ao onTouchEvent() mostra os detalhes de cada interação. Seu app pode usar os dados enviados pelo MotionEvent para determinar se houve um gesto relevante.

Capturar eventos de toque para uma Activity ou View

Para interceptar eventos de toque em uma Activity ou View, modifique o callback onTouchEvent().

O snippet a seguir usa getActionMasked() para extrair a ação que o usuário realizou 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 ao 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 View 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 junto com o método onTouchEvent() descrito acima.

Detectar todos os gestos compatíveis

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

No snippet a seguir, o valor de retorno true dos métodos individuais on<TouchEvent> indica que você processou o evento de toque. O valor de retorno false passa eventos para baixo na pilha de visualizações até que o toque seja processado corretamente.

Execute o seguinte snippet para ter uma ideia de como as ações são acionadas quando você interage com a tela e qual conteúdo do MotionEvent é relacionado a 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 GestureDetector.OnGestureListener.

GestureDetector.SimpleOnGestureListener oferece 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 o GestureDetector.SimpleOnGestureListener e modifica onFling() e onDown().

Independentemente de você usar ou não o GestureDetector.OnGestureListener, é uma prática recomendada implementar um método onDown() que retorne true. 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 presumirá que você quer ignorar o restante do gesto, e os outros métodos do GestureDetector.OnGestureListener nunca serão chamados. Esse comportamento pode causar problemas inesperados no seu app. O único caso em que você deverá retornar false de onDown() é se 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;
            }
        }
    }