Wykrywanie typowych gestów

Wypróbuj sposób tworzenia wiadomości
Jetpack Compose to zalecany zestaw narzędzi UI na Androida. Dowiedz się, jak korzystać z dotyku i wprowadzania w sekcji Utwórz

Gest dotyku ma miejsce, gdy użytkownik zbliży co najmniej 1 palec do ekranu dotykowego, a aplikacja zinterpretuje ten wzorzec dotknięć jako gest. Wykrywanie gestów dzieli się na 2 fazy:

  1. Gromadzenie danych o zdarzeniach dotknięcia.
  2. Interpretowanie danych w celu określenia, czy spełniają one kryteria gestów obsługiwanych przez Twoją aplikację.

Klasy AndroidX

W przykładach w tym dokumencie używane są klas GestureDetectorCompat i MotionEventCompat. Te klasy znajdziesz w bibliotece AndroidaX. Gdy to możliwe, używaj klas AndroidX, aby zapewnić zgodność z wcześniejszymi urządzeniami. MotionEventCompat nie jest zamiennikiem klasy MotionEvent. Udostępnia natomiast statyczne metody narzędziowe, do których przekazujesz obiekt MotionEvent w celu otrzymania działania powiązanego z tym zdarzeniem.

Zbieranie danych

Gdy użytkownik umieści co najmniej 1 palec na ekranie, wywołuje wywołanie zwrotne onTouchEvent() w widoku, który odbiera zdarzenia dotknięcia. Dla każdej sekwencji zdarzeń dotknięcia, np. pozycji, ucisku, rozmiaru i dodania kolejnego palca, rozpoznawanej jako gest, element onTouchEvent() jest wywoływany kilka razy.

Gest rozpoczyna się, gdy użytkownik po raz pierwszy dotknie ekranu, i jest kontynuowany, gdy system śledzi umiejscowienie palca użytkownika, aż po jego zakończeniu. W trakcie tej interakcji dane MotionEvent dostarczane do onTouchEvent() dostarczają szczegółów każdej interakcji. Aplikacja może użyć danych dostarczonych przez funkcję MotionEvent, aby określić, czy dany gest ma dla niej znaczenie.

Rejestrowanie zdarzeń dotknięcia dla aktywności lub widoku

Aby przechwytywać zdarzenia dotknięcia w metodach Activity lub View, zastąp wywołanie zwrotne onTouchEvent().

Ten fragment kodu używa polecenia getAction() do wyodrębnienia działania wykonywanego przez użytkownika z parametru event. Otrzymasz nieprzetworzone dane potrzebne do określenia, czy ma miejsce dany gest, na którym Ci zależy.

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

Ten kod generuje w Logcat komunikaty podobne do tych poniżej, gdy użytkownik klika, dotyka, blokuje i przeciąga:

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

W przypadku gestów niestandardowych możesz samodzielnie przetwarzać te zdarzenia, by określić, czy reprezentują gest, który musisz wykonać. Jeśli jednak Twoja aplikacja używa popularnych gestów, takich jak dwukrotne kliknięcie, dotknięcie i przytrzymanie, przesuwanie palcem itp., możesz wykorzystać klasę GestureDetector. GestureDetector ułatwia wykrywanie typowych gestów bez samodzielnego przetwarzania poszczególnych zdarzeń dotknięcia. Zostało to szczegółowo omówione w artykule Wykrywanie gestów.

Rejestruj zdarzenia dotknięcia w jednym widoku

Zamiast onTouchEvent() możesz dołączyć obiekt View.OnTouchListener do dowolnego obiektu View za pomocą metody setOnTouchListener(). Dzięki temu można nasłuchiwać zdarzeń dotknięcia bez podklasyfikowania istniejącego obiektu View, jak pokazano w tym przykładzie:

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

Uważaj na utworzenie detektora, który zwraca false dla zdarzenia ACTION_DOWN. Jeśli to zrobisz, detektor nie będzie wywoływany dla kolejnych sekwencji zdarzeń ACTION_MOVE i ACTION_UP. Dzieje się tak, ponieważ ACTION_DOWN jest punktem początkowym wszystkich zdarzeń dotknięcia.

Jeśli tworzysz widok niestandardowy, możesz zastąpić onTouchEvent() w sposób opisany wcześniej.

Wykrywanie gestów

Android udostępnia klasę GestureDetector służącą do wykrywania typowych gestów. Obsługuje m.in. onDown(), onLongPress() i onFling(). Funkcji GestureDetector możesz używać w połączeniu z opisaną wcześniej metodą onTouchEvent().

Wykrywanie wszystkich obsługiwanych gestów

Gdy tworzysz instancję obiektu GestureDetectorCompat, jednym z wymaganych parametrów jest klasa, która implementuje interfejs GestureDetector.OnGestureListener. GestureDetector.OnGestureListener powiadamia użytkowników o konkretnym zdarzeniu kliknięcia. Aby umożliwić obiektowi GestureDetector otrzymywanie zdarzeń, zastąp metodę onTouchEvent() widoku lub aktywności i przekaż wszystkie zaobserwowane zdarzenia do instancji detektora.

W tym fragmencie kodu zwracana wartość true z poszczególnych metod on<TouchEvent> wskazuje, że zdarzenie dotknięcia jest obsługiwane. Wartość zwracana false przekazuje zdarzenia w dół przez stos widoków, dopóki dotknięcie nie zostanie obsługiwane.

Jeśli uruchomisz ten fragment kodu w aplikacji testowej, możesz się zorientować, jak będą wywoływane działania podczas interakcji z ekranem dotykowym, i jaka jest zawartość elementu MotionEvent w przypadku każdego zdarzenia dotknięcia. Zobaczysz, ile danych jest generowanych na potrzeby prostych interakcji.

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

Wykrywanie zestawu obsługiwanych gestów

Jeśli chcesz przetworzyć tylko kilka gestów, możesz rozszerzyć pole GestureDetector.SimpleOnGestureListener, zamiast implementować interfejs GestureDetector.OnGestureListener.

GestureDetector.SimpleOnGestureListener udostępnia implementację wszystkich metod on<TouchEvent>, zwracając wartość false w przypadku każdej z nich. Dzięki temu możesz zastąpić tylko metody, na których Ci zależy. Na przykład ten fragment kodu tworzy klasę, która rozszerza zakres GestureDetector.SimpleOnGestureListener i zastępuje onFling() oraz onDown().

Niezależnie od tego, czy używasz metody GestureDetector.OnGestureListener czy GestureDetector.SimpleOnGestureListener, sprawdzoną metodą jest wdrożenie metody onDown(), która zwraca wartość true. Dzieje się tak, ponieważ wszystkie gesty zaczynają się od komunikatu onDown(). Jeśli zwrócisz funkcję false z metody onDown(), tak jak domyślnie GestureDetector.SimpleOnGestureListener, system zakłada, że chcesz zignorować pozostałą część gestu, a inne metody GestureDetector.OnGestureListener nie są wywoływane. Może to spowodować nieoczekiwane problemy w aplikacji. Zwracaj wartość false z usługi onDown() tylko wtedy, gdy naprawdę chcesz zignorować cały gest.

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

Dodatkowe materiały