Detectar gestos comuns

Experimente trabalhar com o Compose
O Jetpack Compose é o kit de ferramentas de interface recomendado para o Android. Aprenda a usar o toque e a entrada no Compose.

Um gesto de toque acontece quando um usuário coloca um ou mais dedos na tela sensível ao toque e o app interpreta esse padrão de toques como um gesto. Há duas fases na detecção de gestos:

  1. Coleta de dados de eventos de toque.
  2. Interpretação dos dados para determinar se eles atendem aos critérios dos gestos compatíveis com o app.

Classes do AndroidX

Os exemplos neste documento usam as classes GestureDetectorCompat e MotionEventCompat. Essas classes estão na biblioteca AndroidX. Use as classes do AndroidX sempre que possível para oferecer compatibilidade com dispositivos mais antigos. MotionEventCompat é não uma substituição para a MotionEvent classe. 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 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 recebe os eventos de toque. Para cada sequência de eventos de toque, como posição, pressão, tamanho e adição de outro dedo, 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 do último dedo 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 acontece.

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 de código a seguir usa getAction() para extrair a ação que o usuário executa do parâmetro event. Essa ação disponibiliza os dados brutos necessários para determinar se um gesto relevante ocorre.

Kotlin

class MainActivity : Activity() {
    ...
    // This example shows an Activity. You can use the same approach if you are 
    // subclassing a View.
    override fun onTouchEvent(event: MotionEvent): Boolean {
        return when (event.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. You can use the same approach if you are
// subclassing a View.
@Override
public boolean onTouchEvent(MotionEvent event){
    switch(event.getAction()) {
        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);
    }
}

Esse código produz mensagens como as seguintes no Logcat quando o usuário toca, toca e mantém pressionado e arrasta:

GESTURES D   Action was DOWN
GESTURES D   Action was UP
GESTURES D   Action was MOVE

Para gestos personalizados, você pode fazer o próprio processamento nesses eventos para determinar se eles representam um gesto que você precisa processar. No entanto, se o app usa gestos comuns, como tocar duas vezes, tocar e pressionar, deslizar rapidamente 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. Isso é discutido com mais detalhes em Detectar gestos.

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

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

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 sequência 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 anteriormente.

Detectar gestos

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

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. O 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 o evento de toque foi processado. Um valor de retorno de false transmite os eventos pela pilha de visualização até que o toque seja processado.

Se você executar o snippet a seguir em um app de teste, poderá ter uma ideia de como as ações são acionadas quando você interage com a tela sensível ao toque e qual é o conteúdo do MotionEvent para cada evento de toque. Em seguida, você verá quantos dados estão sendo gerados para 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ê quiser apenas processar apenas alguns gestos, poderá estender GestureDetector.SimpleOnGestureListener em vez de implementar a interface GestureDetector.OnGestureListener.

GestureDetector.SimpleOnGestureListener fornece uma implementação para todos os métodos on<TouchEvent> retornando false para todos eles. Isso permite que você modifique apenas os métodos do seu interesse. Por exemplo, o snippet de código a seguir cria uma classe que estende GestureDetector.SimpleOnGestureListener e modifica onFling() e onDown().

Quer você use GestureDetector.OnGestureListener ou GestureDetector.SimpleOnGestureListener, 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 presumirá que você quer ignorar o restante do gesto, e os outros métodos de GestureDetector.OnGestureListener não serão chamados. Isso pode causar problemas inesperados no seu app. Retorne false de onDown() somente 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){
        if (this.mDetector.onTouchEvent(event)) {
              return true;
        }
        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;
        }
    }
}

Outros recursos