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:
- Gromadzenie danych o zdarzeniach dotknięcia.
- 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; } } }