Розробка бокового меню (Drawer/Sidebar) для мобільного додатку
Бокове меню — один із тих компонентів, які виглядають просто, але ламаються саме там, де не очікуєш. На Android DrawerLayout із NavigationView працює передбачувано, поки не з'явиться вложена навігація або кастомний заголовок з аватаром. На iOS UINavigationController + кастомний бічний панель починає конфліктувати з gesture recognizer'ами, і swipe "назад" перехоплюється раніше, ніж drawer встигає розкритися.
У React Native ситуація складніша: @react-navigation/drawer побудована на react-native-gesture-handler та react-native-reanimated. Якщо версії несумісні або GestureHandlerRootView обгорнута неправильно, отримуємо тиху помилку — drawer відкривається жестом тільки на Android, а на iOS не реагує взагалі.
Де частіше за все виникають проблеми
Конфлікти жестів. На iOS Edge Swipe від лівого краю екрана перехоплюється системою для навігації назад. Drawer з таким же жестом потрапляє у колізію. Вирішується через UIScreenEdgePanGestureRecognizer з явним require(toFail:) — вказуємо, що навігаційний жест повинен провалитися до того, як drawer отримає управління. У React Native аналогічно: edgeWidth у конфігу drawer та screenEdgeGestureEnabled: false на потрібних екранах.
Продуктивність анімації. Стандартний TranslateX через JS-потік у React Native дає помітний lag на середньорангових Android при 60fps. Переносимо анімацію в Reanimated 3 — useAnimatedStyle + withSpring або withTiming виконуються прямо в UI-потоці. Різниця помітна на Redmi Note 10.
Стан і навігація. Якщо drawer містить вложений Stack Navigator, історія при закриванні/відкриванні меню іноді скидається. Використовуємо drawerType: 'permanent' на планшетах та lazy: false для критичних екранів, щоб не втратити стан компонентів.
Як ми це реалізуємо
Для Flutter: Scaffold.drawer + DrawerHeader — базовий випадок. Для анімованого drawer з кастомною поведінкою — AnimationController + SlideTransition, прив'язаний до GestureDetector з onHorizontalDragUpdate. Це дає повний контроль над кривою анімації та порогами swipe.
Для нативного Android: DrawerLayout + NavigationView з NavigationUI.setupWithNavController() — інтеграція з Jetpack Navigation Component. Якщо потрібен кастомний вид пунктів меню, заміняємо NavigationView на RecyclerView всередині drawer, використовуємо DiffUtil для оновлень.
На iOS з SwiftUI: NavigationSplitView починаючи з iOS 16 закриває більшість кейсів для iPad/iPhone. Для тоншого контролю — кастомний overlay через ZStack + offset + DragGesture, з haptic feedback через UIImpactFeedbackGenerator при повному відкриванні.
Типовий кейс з практики: додаток доставки їжі, Flutter, бокове меню з профілем користувача, історією замовлень та налаштуваннями. Проблема — при швидкому swipe drawer "залипав" у напіввідкритому стані на Android. Причина: flingVelocity threshold був виставлений занадто високо. Знизили до 300 логічних пікселів/сек, додали snap-анімацію через AnimationController.fling() — drawer став закриватися/відкриватися до кінця при будь-якому резкому swipe.
Що входить у роботу
- Реалізація відкриття/закриття через кнопку hamburger та swipe жест
- Налаштування overlay (затемнення фону) з правильним z-index
- Кастомний header: аватар, ім'я, email з підтримкою оновлення в реальному часі
- Список пунктів меню з іконками, badge-лічильниками та активним станом
- Інтеграція з системою навігації додатку (React Navigation, Jetpack Nav Component, Coordinator Pattern)
- Адаптація під планшети: persistent sidebar замість overlay drawer
- Accessibility:
contentDescription,accessibilityLabel, підтримка TalkBack/VoiceOver
Строки
Базова реалізація drawer з навігацією: 1–2 дні. З кастомним дизайном, анімаціями та інтеграцією в існуючу архітектуру навігації — 2–3 дні. Вартість розраховується індивідуально після аналізу вимог та поточної структури проекту.







