Жестова навігація — це система керування мобільним застосунком через сенсорні жести, яка замінила апаратні кнопки на більшості сучасних смартфонів. За даними Android Developers (2024), жестова навігація стала стандартом починаючи з Android 10, а Apple перейшла на жести ще з iPhone X у 2017 році. Система включає swipe, pinch, double tap та long press — кожен жест має певне призначення в контексті екрана. Розуміння архітектури жестової навігації необхідне для створення інтуїтивних та чуйних інтерфейсів.
Головне
Жестова навігація — це метод взаємодії користувача з мобільним пристроєм через послідовності дотиків, рухів та натискань, що розпізнаються сенсорним екраном. На відміну від традиційних кнопок, жести не мають фіксованого положення на екрані та розпізнаються за шаблоном руху: свайп від краю, пінч двома пальцями, довге натискання.
Перехід на жестову навігацію розпочався з iPhone X (2017), де Apple повністю прибрала кнопку Home, замінивши її свайпом від нижнього краю. Google наслідувала тренд в Android 10 (2020), запропонувавши користувачам вибір: трьохкнопкова навігація, навігація двома кнопками та повноцінна жестова. За даними StatCounter (2025), понад 75% Android-пристроїв та 95% iOS-пристроїв використовують жестову навігацію.
Архітектурно розпізнавання жестів складається з трьох етапів: Capture (захоплення сенсорних подій), Recognition (визначення шаблону руху) та Action (виконання призначеної дії). На системному рівні Android та iOS мають вбудовані обробники для базових навігаційних жестів — свайп назад, перехід на домашній екран, відкриття перемикача застосунків. Розробнику потрібно лише коректно інтегрувати свої жести в цю систему.
Всі сенсорні жести можна розділити на три категорії за метою та способом виконання. Кожен тип має свої правила обробки та рекомендації щодо застосування в інтерфейсі.
Навігаційні жести керують переміщенням між екранами: свайп від лівого краю для повернення назад (iOS), свайп вгору для відкриття перемикача застосунків, свайп від нижнього краю для повернення на домашній екран. Ці жести обробляються системою на рівні Window і не повинні конфліктувати з користувацькими жестами всередині застосунку.
Маніпуляційні жести змінюють положення, розмір або орієнтацію об'єктів на екрані. Pinch (масштабування двома пальцями), Rotate (поворот), Pan (перетягування), Swipe (швидкий свайп для гортання) — всі ці жести обробляються на рівні View або Composable і не зачіпають системну навігацію.
Контекстні жести активують додаткові функції без переходу на інший екран. Long press (довге натискання для контекстного меню), Double tap (подвійне натискання для лайка або масштабування), Edge swipe (свайп від краю для відкриття Drawer). Ці жести потребують найбільш обережної реалізації, оскільки можуть перетинатися з системними навігаційними жестами.
| Тип жесту | Приклад | Рівень обробки | Конфлікт з системою |
|---|---|---|---|
| Навігаційні | Свайп назад | Window / System | Первинний |
| Маніпуляційні | Pinch zoom | View | Немає |
| Контекстні | Long press | View | Можливий |
Android SDK надає багаторівневу систему обробки жестів, починаючи від низькорівневих MotionEvent до високорівневих GestureDetector та GestureOverlayView. Правильний вибір рівня залежить від складності жесту та вимог до продуктивності.
GestureDetector — це високорівневий клас, який перетворює послідовність MotionEvent на конкретні жести: onDown, onShowPress, onSingleTapUp, onScroll, onLongPress, onFling. Розробник перевизначає потрібні методи GestureDetector.SimpleOnGestureListener та отримує готову розпізнану подію. GestureDetector рекомендується для всіх стандартних жестів, крім масштабування — для нього є 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 — механізм розширення області дотику для View. Використовується, коли цільовий елемент менший за мінімальний розмір дотику 48dp. Наприклад, маленька кнопка «Закрити» в кутку екрана отримує TouchDelegate, що розширює її hit area без зміни видимого розміру. Google рекомендує TouchDelegate для всіх інтерактивних елементів менше 48x48dp.
Compose надає модифікатори для обробки жестів: clickable, draggable, swipeable, combinedClickable (для double tap та long press). Всередині Compose використовує PointerInputScope для низькорівневої обробки, але більшості розробників достатньо високорівневих модифікаторів. Для кастомних жестів використовується pointerInput з awaitPointerEvent.
iOS SDK використовує архітектуру UIGestureRecognizer — абстрактного базового класу, який аналізує послідовність UITouch та визначає, чи відповідає вона відомому жесту. Apple рекомендує використовувати стандартні recognizer всюди, де можливо, і тільки для унікальних жестів створювати кастомні підкласи.
UIKit надає набір готових recognizer: UITapGestureRecognizer, UISwipeGestureRecognizer, UIPanGestureRecognizer, UIPinchGestureRecognizer, UIRotationGestureRecognizer, UILongPressGestureRecognizer. Кожен recognizer має states (possible, began, changed, ended, cancelled, failed) — розробник відстежує стан для реакції на різних етапах жесту.
let swipeBack = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipeBack)
)
swipeBack.direction = .right
view.addGestureRecognizer(swipeBack)
@objc func handleSwipeBack() {
navigationController?.popViewController(animated: true)
}
SwiftUI використовує декларативні модифікатори жестів, аналогічно Compose: onTapGesture, onLongPressGesture, MagnificationGesture, RotationGesture, DragGesture. SwiftUI автоматично обробляє конфлікти між жестами через simultaneousGesture, sequencedGesture та exclusiveGesture — модифікатори, що визначають пріоритет жестів при одночасному спрацьовуванні.
Image("photo")
.gesture(
MagnificationGesture()
.onChanged { scale in
self.currentScale = scale
}
.sequenced(before: DragGesture())
)
InteractivePopGestureRecognizer — системний recognizer, що керує свайпом назад в UINavigationController. За замовчуванням він активний для всіх екранів, крім кореневого. Якщо ваш застосунок використовує кастомний NavigationBar, interactivePopGestureRecognizer може перестати працювати — знадобиться його програмна активація через navigationController.interactivePopGestureRecognizer?.delegate.
Конфлікт жестів — одна з найскладніших проблем у gesture navigation. Коли користувацький жест (наприклад, відкриття Drawer свайпом від лівого краю) збігається з системним жестом (свайп назад на iOS або Android), система повинна визначити, який жест має пріоритет. Правильна обробка цього конфлікту критична для UX.
Android 10+ дозволяє застосунку зарезервувати області екрана для своїх жестів через WindowInsets. Використовуйте ViewCompat.setSystemGestureExclusionRects для вказання регіонів, де системні жести не повинні спрацьовувати. Наприклад, для Drawer зліва можна виключити лівий край екрана (шириною до 200dp) із системного свайпу назад. Google встановила ліміт: можна виключити до 200dp по кожній стороні.
val exclusionRect = Rect(0, 0, 200, height)
ViewCompat.setSystemGestureExclusionRects(
drawerView,
listOf(SystemGestureExclusionRect(exclusionRect))
)
iOS надає метод gestureRecognizerShouldBegin в UIGestureRecognizerDelegate, який дозволяє кастомному recognizer вирішити, чи повинен він почати розпізнавання. Для Drawer свайпу від лівого краю можна перевірити положення дотику: якщо користувач тягне Drawer (відстань більше threshold), кастомний жест перехоплює керування. Якщо жест не розпізнано, система повертає керування InteractivePopGestureRecognizer.
Жестова навігація потребує уважного проєктування, особливо на пристроях із системною жестовою навігацією. Розглянемо часті помилки та рекомендації щодо їх усунення.
Найпоширеніша помилка — розміщення інтерактивних елементів або реалізація кастомних свайпів у зонах системних жестів (лівий та правий краї, нижній край). Користувач намагається виконати дію, а замість цього спрацьовує системна навігація. Завжди передбачайте відступи для системних жестів та обробляйте конфлікти через exclusion rects.
Параметри жестів (velocity threshold, minimum distance) за замовчуванням відрізняються між платформами. Якщо ваш застосунок кросплатформний, не копіюйте параметри з однієї платформи на іншу — тестуйте кожен жест окремо на Android та iOS. Flutter та React Native автоматично адаптують деякі параметри, але не всі.
Кожен жест повинен супроводжуватися візуальним зворотним зв'язком: зміна кольору, трансформація, анімація. Користувач повинен розуміти, що жест розпізнано і дія виконується. На iOS системні recognizer автоматично дають haptic feedback, на Android його потрібно додавати через HapticFeedbackConstants.
Не всі користувачі можуть виконувати жести — люди з обмеженою моторикою використовують VoiceOver та TalkBack для навігації. Для кожного жесту повинна бути кнопкова альтернатива. Google та Apple вимагають, щоб всі жестові дії дублювалися доступними для accessibility елементами керування.
Часто задавані питання
iOS — Apple впровадила жестову навігацію з iPhone X у 2017 році, замінивши кнопку Home свайпом від нижнього краю. Android наслідувала приклад в Android 10 (2020), запропонувавши жести як альтернативу кнопкам.
Свайп — швидкий рух з високою velocity (пікселів/сек) та переривчастий. Скрол — повільний рух з низькою velocity та безперервний. Використовуйте velocityX/Y для розрізнення: поріг зазвичай 500—1000 px/s залежно від платформи.
Ні — Android та iOS не дозволяють застосунку вимкнути системну жестову навігацію. Ви можете лише зарезервувати області екрана через exclusion rects (Android) або gestureRecognizerShouldBegin (iOS).
Android Emulator підтримує мультитач через Ctrl+клік (додавання другого пальця). iOS Simulator — через Option+клік для двох пальців. Flutter test використовує WidgetTester.timedDrag для симуляції свайпів в unit-тестах.
Gesture War — конфлікт між двома recognizer, коли обидва намагаються обробити один дотик. Уникайте через пріоритети: в iOS використовуйте require(toFail:), в Compose — sequentialGesture та exclusiveGesture. Flutter використовує GestureArena для автоматичного вирішення.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також