Gesture Navigation — to system sterowania aplikacją mobilną za pomocą gestów dotykowych, który zastąpił przyciski sprzętowe w większości nowoczesnych smartfonów. Według Android Developers (2024), nawigacja gestami stała się standardem od Androida 10, a Apple przeszło na gesty już od iPhone X w 2017 roku. System obejmuje swipe, pinch, double tap i long press — każdy gest ma określone przeznaczenie w kontekście ekranu. Zrozumienie architektury nawigacji gestami jest niezbędne do tworzenia intuicyjnych i responsywnych interfejsów.
Najważniejsze
Gesture Navigation — to metoda interakcji użytkownika z urządzeniem mobilnym poprzez sekwencje dotknięć, ruchów i nacisków rozpoznawane przez ekran dotykowy. W przeciwieństwie do tradycyjnych przycisków, gesty nie mają stałego położenia na ekranie i są rozpoznawane według wzorca ruchu: swip od krawędzi, pinch dwoma palcami, długie naciśnięcie.
Przejście na nawigację gestami rozpoczęło się od iPhone X (2017), gdzie Apple całkowicie usunęło przycisk Home, zastępując go swipem od dolnej krawędzi. Google poszło za trendem w Androidzie 10 (2020), oferując użytkownikom wybór: nawigacja trzema przyciskami, nawigacja dwoma przyciskami i pełna nawigacja gestami. Według StatCounter (2025), ponad 75% urządzeń z Androidem i 95% urządzeń z iOS używa nawigacji gestami.
Architektonicznie rozpoznawanie gestów składa się z trzech etapów: Capture (przechwytywanie zdarzeń dotykowych), Recognition (określenie wzorca ruchu) i Action (wykonanie przypisanej akcji). Na poziomie systemowym Android i iOS mają wbudowane obsługi dla podstawowych gestów nawigacyjnych — swip wstecz, przejście do ekranu głównego, otwarcie przełącznika aplikacji. Deweloper musi jedynie poprawnie zintegrować swoje gesty z tym systemem.
Wszystkie gesty dotykowe można podzielić na trzy kategorie według celu i sposobu wykonania. Każdy typ ma swoje zasady obsługi i zalecenia dotyczące zastosowania w interfejsie.
Gesty nawigacyjne zarządzają przemieszczaniem się między ekranami: swip od lewej krawędzi do powrotu (iOS), swip w górę do otwarcia przełącznika aplikacji, swip od dolnej krawędzi do powrotu na ekran główny. Te gesty są obsługiwane przez system na poziomie Window i nie powinny kolidować z gestami użytkownika wewnątrz aplikacji.
Gesty manipulacyjne zmieniają położenie, rozmiar lub orientację obiektów na ekranie. Pinch (skalowanie dwoma palcami), Rotate (obrót), Pan (przeciąganie), Swipe (szybki swip do przewijania) — wszystkie te gesty są obsługiwane na poziomie View lub Composabale i nie wpływają na nawigację systemową.
Gesty kontekstowe aktywują dodatkowe funkcje bez przechodzenia do innego ekranu. Long press (długie naciśnięcie dla menu kontekstowego), Double tap (podwójne naciśnięcie do polubienia lub skalowania), Edge swipe (swip od krawędzi do otwarcia Drawer). Te gesty wymagają najbardziej starannej implementacji, ponieważ mogą nakładać się na systemowe gesty nawigacyjne.
| Typ gestu | Przykład | Poziom obsługi | Konflikt z systemem |
|---|---|---|---|
| Nawigacyjne | Swip wstecz | Window / System | Pierwotny |
| Manipulacyjne | Pinch zoom | View | Nie |
| Kontekstowe | Long press | View | Możliwy |
Android SDK zapewnia wielopoziomowy system obsługi gestów, począwszy od niskopoziomowych MotionEvent do wysokopoziomowych GestureDetector i GestureOverlayView. Właściwy wybór poziomu zależy od złożoności gestu i wymagań dotyczących wydajności.
GestureDetector — to wysokopoziomowa klasa, która przekształca sekwencję MotionEvent w konkretne gesty: onDown, onShowPress, onSingleTapUp, onScroll, onLongPress, onFling. Deweloper nadpisuje potrzebne metody GestureDetector.SimpleOnGestureListener i otrzymuje gotowe rozpoznane zdarzenie. GestureDetector jest zalecany dla wszystkich standardowych gestów, z wyjątkiem skalowania — do tego służy ScaleGestureDetector.
val gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
override fun onFling(
e1: MotionEvent?, e2: MotionEvent,
velocityX: Float, velocityY: Float
): Boolean {
val deltaX = e2.x - (e1?.x ?: 0f)
return if (Math.abs(deltaX) > Math.abs(e2.y - (e1?.y ?: 0f))) {
if (deltaX > 0) onSwipeRight() else onSwipeLeft()
true
} else false
}
})
view.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
TouchDelegate — mechanizm rozszerzania obszaru dotyku dla View. Używany, gdy docelowy element jest mniejszy niż minimalny rozmiar dotyku 48dp. Na przykład mały przycisk „Zamknij" w rogu ekranu otrzymuje TouchDelegate, który rozszerza jego obszar trafienia bez zmiany widocznego rozmiaru. Google zaleca TouchDelegate dla wszystkich elementów interaktywnych mniejszych niż 48x48dp.
Compose zapewnia modyfikatory do obsługi gestów: clickable, draggable, swipeable, combinedClickable (dla double tap i long press). Wewnętrznie Compose używa PointerInputScope do niskopoziomowej obsługi, ale większości deweloperów wystarczą wysokopoziomowe modyfikatory. Do niestandardowych gestów używa się pointerInput z awaitPointerEvent.
iOS SDK używa architektury UIGestureRecognizer — abstrakcyjnej klasy bazowej, która analizuje sekwencję UITouch i określa, czy odpowiada ona znanemu gestowi. Apple zaleca używanie standardowych recognizer wszędzie, gdzie to możliwe, a tylko dla unikalnych gestów tworzyć niestandardowe podklasy.
UIKit zapewnia zestaw gotowych recognizer: UITapGestureRecognizer, UISwipeGestureRecognizer, UIPanGestureRecognizer, UIPinchGestureRecognizer, UIRotationGestureRecognizer, UILongPressGestureRecognizer. Każdy recognizer ma stany (possible, began, changed, ended, cancelled, failed) — deweloper śledzi stan, aby reagować na różnych etapach gestu.
let swipeBack = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipeBack)
)
swipeBack.direction = .right
view.addGestureRecognizer(swipeBack)
@objc func handleSwipeBack() {
navigationController?.popViewController(animated: true)
}
SwiftUI używa deklaratywnych modyfikatorów gestów, podobnie jak Compose: onTapGesture, onLongPressGesture, MagnificationGesture, RotationGesture, DragGesture. SwiftUI automatycznie obsługuje konflikty między gestami poprzez simultaneousGesture, sequencedGesture i exclusiveGesture — modyfikatory określające priorytet gestów przy jednoczesnym wyzwoleniu.
Image("photo")
.gesture(
MagnificationGesture()
.onChanged { scale in
self.currentScale = scale
}
.sequenced(before: DragGesture())
)
InteractivePopGestureRecognizer — systemowy recognizer zarządzający swipem wstecz w UINavigationController. Domyślnie jest aktywny dla wszystkich ekranów z wyjątkiem głównego. Jeśli twoja aplikacja używa niestandardowego NavigationBar, interactivePopGestureRecognizer może przestać działać — wymagana będzie jego programowa aktywacja przez navigationController.interactivePopGestureRecognizer?.delegate.
Konflikt gestów — jeden z najtrudniejszych problemów w nawigacji gestami. Gdy gest użytkownika (na przykład otwarcie Drawer swipem od lewej krawędzi) pokrywa się z gestem systemowym (swip wstecz na iOS lub Androidzie), system musi określić, który gest ma priorytet. Prawidłowa obsługa tego konfliktu jest kluczowa dla UX.
Android 10+ pozwala aplikacji zarezerwować obszary ekranu dla swoich gestów przez WindowInsets. Użyj ViewCompat.setSystemGestureExclusionRects do wskazania regionów, w których gesty systemowe nie powinny działać. Na przykład dla Drawer po lewej stronie można wykluczyć lewą krawędź ekranu (szerokość do 200dp) z systemowego swipa wstecz. Google ustanowiło limit: można wykluczyć do 200dp z każdej strony.
val exclusionRect = Rect(0, 0, 200, height)
ViewCompat.setSystemGestureExclusionRects(
drawerView,
listOf(SystemGestureExclusionRect(exclusionRect))
)
iOS udostępnia metodę gestureRecognizerShouldBegin w UIGestureRecognizerDelegate, która pozwala niestandardowemu recognizer zdecydować, czy powinien rozpocząć rozpoznawanie. Dla Drawer swipa od lewej krawędzi można sprawdzić położenie dotyku: jeśli użytkownik ciągnie Drawer (odległość większa niż threshold), niestandardowy gest przechwytuje sterowanie. Jeśli gest nie został rozpoznany, system zwraca sterowanie do InteractivePopGestureRecognizer.
Gesture Navigation wymaga starannego projektowania, szczególnie na urządzeniach z systemową nawigacją gestami. Rozważmy częste błędy i zalecenia dotyczące ich eliminacji.
Najczęstszy błąd — umieszczanie elementów interaktywnych lub implementacja niestandardowych swipów w strefach gestów systemowych (lewa i prawa krawędź, dolna krawędź). Użytkownik próbuje wykonać akcję, a zamiast tego uruchamia się nawigacja systemowa. Zawsze przewiduj odstępy dla gestów systemowych i obsługuj konflikty przez exclusion rects.
Parametry gestów (velocity threshold, minimum distance) domyślnie różnią się między platformami. Jeśli twoja aplikacja jest wieloplatformowa, nie kopiuj parametrów z jednej platformy na drugą — testuj każdy gest osobno na Androidzie i iOS. Flutter i React Native automatycznie dostosowują niektóre parametry, ale nie wszystkie.
Każdy gest powinien być wspierany wizualnym sprzężeniem zwrotnym: zmiana koloru, transformacja, animacja. Użytkownik musi rozumieć, że gest został rozpoznany i akcja jest wykonywana. Na iOS systemowe recognizer automatycznie zapewniają haptic feedback, na Androidzie trzeba go dodać przez HapticFeedbackConstants.
Nie wszyscy użytkownicy mogą wykonywać gesty — osoby z ograniczoną motoryką używają VoiceOver i TalkBack do nawigacji. Dla każdego gestu powinna istnieć alternatywa przyciskowa. Google i Apple wymagają, aby wszystkie akcje gestowe były powielane przez elementy sterowania dostępne dla accessibility.
Często zadawane pytania
iOS — Apple wdrożyło nawigację gestami z iPhone X w 2017 roku, zastępując przycisk Home swipem od dolnej krawędzi. Android poszedł za przykładem w Androidzie 10 (2020), oferując gesty jako alternatywę dla przycisków.
Swip — szybki ruch z wysoką velocity (pikseli/sek) i przerywany. Scroll — wolny ruch z niską velocity i ciągły. Użyj velocityX/Y do rozróżnienia: próg wynosi zazwyczaj 500-1000 px/s w zależności od platformy.
Nie — Android i iOS nie pozwalają aplikacji wyłączyć systemowej nawigacji gestami. Możesz tylko zarezerwować obszary ekranu przez exclusion rects (Android) lub gestureRecognizerShouldBegin (iOS).
Android Emulator obsługuje multitouch przez Ctrl+klik (dodanie drugiego palca). iOS Simulator — przez Option+klik dla dwóch palców. Flutter test używa WidgetTester.timedDrag do symulacji swipów w testach jednostkowych.
Gesture War — konflikt między dwoma recognizer, gdy oba próbują obsłużyć jeden dotyk. Unikaj przez priorytety: w iOS używaj require(toFail:), w Compose — sequentialGesture i exclusiveGesture. Flutter używa GestureArena do automatycznego rozwiązywania.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również