Gesture Navigation — سیستمی برای کنترل اپلیکیشن موبایل از طریق ژستهای لمسی است که دکمههای سختافزاری را در بیشتر گوشیهای هوشمند مدرن جایگزین کرده است. به گفته Android Developers (2024)، ناوبری ژستی از Android 10 به بعد به استاندارد تبدیل شد و Apple از iPhone X در سال 2017 به ژستها روی آورد. این سیستم شامل swipe، pinch، double tap و long press است — هر ژست در زمینه صفحه هدف مشخصی دارد. درک معماری ناوبری ژستی برای ایجاد رابطهای بصری و واکنشگرا ضروری است.
نکات اصلی
Gesture Navigation — روشی برای تعامل کاربر با دستگاه موبایل از طریق توالی لمسها، حرکات و فشارهایی است که توسط صفحه لمسی تشخیص داده میشوند. برخلاف دکمههای سنتی، ژستها موقعیت ثابتی روی صفحه ندارند و بر اساس الگوی حرکت تشخیص داده میشوند: سوایپ از لبه، پینچ با دو انگشت، فشار طولانی.
گذار به ناوبری ژستی با 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 یا Composabale پردازش میشوند و بر ناوبری سیستمی تأثیر نمیگذارند.
ژستهای زمینهای بدون رفتن به صفحه دیگر، عملکردهای اضافی را فعال میکنند. 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 دریافت میکند که ناحیه ضربه آن را بدون تغییر اندازه ظاهری گسترش میدهد. 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 حالتهایی دارد (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 دارد.
تعارض ژستها — یکی از دشوارترین مسائل در ناوبری ژستی است. وقتی ژست کاربر (مثلاً باز کردن Drawer با سوایپ از لبه چپ) با ژست سیستمی (سوایپ به عقب در iOS یا Android) همپوشانی دارد، سیستم باید تعیین کند کدام ژست اولویت دارد. مدیریت صحیح این تعارض برای تجربه کاربری حیاتی است.
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 برمیگرداند.
Gesture Navigation نیاز به طراحی دقیق دارد، به خصوص در دستگاههایی با ناوبری ژستی سیستمی. بیایید اشتباهات رایج و توصیههایی برای رفع آنها را بررسی کنیم.
رایجترین اشتباه — قرار دادن عناصر تعاملی یا پیادهسازی سوایپهای سفارشی در مناطق ژستهای سیستمی (لبههای چپ و راست، لبه پایین). کاربر سعی میکند عملی انجام دهد اما در عوض ناوبری سیستمی فعال میشود. همیشه برای ژستهای سیستمی فاصله در نظر بگیرید و تعارضات را از طریق exclusion rects مدیریت کنید.
پارامترهای ژست (velocity threshold، minimum distance) به طور پیشفرض بین پلتفرمها متفاوت است. اگر اپلیکیشن شما چندپلتفرمی است، پارامترها را از یک پلتفرم به دیگری کپی نکنید — هر ژست را جداگانه در Android و iOS تست کنید. Flutter و React Native برخی پارامترها را به طور خودکار تطبیق میدهند، اما نه همه.
هر ژست باید با بازخورد بصری همراه باشد: تغییر رنگ، تبدیل، انیمیشن. کاربر باید بفهمد که ژست تشخیص داده شده و عمل در حال اجراست. در iOS، recognizer سیستمی به طور خودکار بازخورد لمسی (haptic) میدهند، در Android باید آن را از طریق HapticFeedbackConstants اضافه کرد.
همه کاربران نمیتوانند ژستها را انجام دهند — افراد با محدودیت حرکتی از VoiceOver و TalkBack برای ناوبری استفاده میکنند. برای هر ژست باید یک جایگزین دکمهای وجود داشته باشد. Google و Apple نیاز دارند که همه اقدامات ژستی توسط عناصر کنترلی قابل دسترس تکراری شوند.
سوالات متداول
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 برای شبیهسازی سوایپها در تستهای واحد استفاده میکند.
Gesture War — تعارض بین دو recognizer وقتی هر دو سعی میکنند یک لمس را پردازش کنند. با اولویتها از آن جلوگیری کنید: در iOS از require(toFail:) استفاده کنید، در Compose — sequentialGesture و exclusiveGesture. Flutter از GestureArena برای حل خودکار استفاده میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید