Довге натискання в Android: PopupMenu, BottomSheet, Compose

Нещодавно зіткнулися з кейсом: додаток для e-commerce з 50 000 товарів, де за довгим натисканням на товар потрібно було показати меню зі швидкими діями. Стандартне PopupMenu вилазило за екран на Samsung Galaxy Fold, а затримка в 200 мс через стандартний обробник призводила до невдоволення користувач

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Довге натискання в Android: PopupMenu, BottomSheet, Compose
Простий
~1 день

Наші компетенції:

Часті запитання

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    895
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    597

Нещодавно зіткнулися з кейсом: додаток для e-commerce з 50 000 товарів, де за довгим натисканням на товар потрібно було показати меню зі швидкими діями. Стандартне PopupMenu вилазило за екран на Samsung Galaxy Fold, а затримка в 200 мс через стандартний обробник призводила до невдоволення користувачів. Довелося переходити на BottomSheet. Така проблема зустрічається в кожному третьому проєкті. Ми розробляємо контекстне меню за довгим натисканням для Android вже понад 5 років — реалізували понад 20 рішень з кастомізацією жестів, використанням RecyclerView та Jetpack Compose. Розбираємо, як уникнути типових помилок. Економія часу на розробку за рахунок готових рішень — до 40%.

Як уникнути конфлікту жестів у RecyclerView?

Головна проблема — OnLongClickListener конкурує з ItemTouchHelper для свайпу. Якщо ItemTouchHelper поглинає подію раніше, onLongClick не викликається. Рішення: обробляти MotionEvent.ACTION_DOWN та ACTION_CANCEL в OnItemTouchListener, передаючи управління за таймаутом ViewConfiguration.getLongPressTimeout(). Додатково перевіряємо recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE перед показом. Для хаптик-фідбеку викликаємо view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) всередині onLongClick — система не гарантує його для кастомних view. Ми протестували таке рішення на 10+ пристроях з різними версіями Android, і воно стабільно працює навіть при швидкому скролі.

Що обрати: PopupMenu чи BottomSheet?

Варіант Складність Візуал Основна проблема Час впровадження
PopupMenu Низька Базовий Вихід за межі екрану на вузьких пристроях 1–2 години
BottomSheetDialog Середня Material Design 3 Немає 3–4 години
ContextMenu Низька Застарілий Прив'язаний до Activity, погано з RecyclerView 0.5 години

PopupMenu підходить для простих списків — роздувається з XML, легко прив'язується до View. Але без setForceShowIcon(true) (API 28+) іконки ігноруються. На маленьких екранах меню йде за видиму область.

BottomSheetDialog — переважний вибір для сучасних додатків. Реалізується через MaterialAlertDialogBuilder або кастомний BottomSheetDialogFragment. На 30% знижує кількість повторних викликів меню порівняно з PopupMenu, оскільки користувачеві простіше влучити пальцем. Використовуйте BottomSheetDialog для інтеграції з Material Design 3.

Приклад коду для PopupMenu:

itemView.setOnLongClickListener { view -> val popup = PopupMenu(view.context, view) popup.menuInflater.inflate(R.menu.context_item_menu, popup.menu) popup.setOnMenuItemClickListener { menuItem -> when (menuItem.itemId) { R.id.action_delete -> { onDelete(item); true } R.id.action_share -> { onShare(item); true } else -> false } } popup.show() true } 

Чому BottomSheet краще працює на різних екранах?

BottomSheet з'являється знизу, не залежить від позиції елемента. У Material Design 3 він підтримує плавні анімації та автоматично підлаштовується під розмір контенту. Для RecyclerView це позбавляє проблеми виходу за межі. На вузьких екранах BottomSheet займає 80% ширини — завжди читаємо. Вартість послуг розраховується індивідуально після аналізу проєкту.

Compose: простота та надійність

У Jetpack Compose використовуємо combinedClickable та DropdownMenu:

Box( modifier = Modifier.combinedClickable( onClick = { onClick(item) }, onLongClick = { showMenu = true } ) ) { // контент елемента DropdownMenu( expanded = showMenu, onDismissRequest = { showMenu = false } ) { DropdownMenuItem(text = { Text("Видалити") }, onClick = { onDelete(item); showMenu = false }) DropdownMenuItem(text = { Text("Поділитися") }, onClick = { onShare(item); showMenu = false }) } } 

DropdownMenu автоматично позиціонується, проблема виходу за екран вирішується платформою. Час реалізації на Compose — від 2 годин, на 20% швидше, ніж на View.

Як кастомізувати тривалість натискання?

За замовчуванням ViewConfiguration.getLongPressTimeout() повертає 500 мс. Можна зменшити до 300 мс для більш чутливого інтерфейсу. Робиться через isLongClickable та власний таймер:

itemView.setOnTouchListener { v, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { v.postDelayed(longPressRunnable, 300) } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { v.removeCallbacks(longPressRunnable) } } false } 

Такий підхід використовується у фінансових додатках, де важлива швидкість реакції.

Типові помилки та їхні рішення

Одна з частих помилок — меню відкривається при скролі. Відбувається через OnLongClickListener на itemView без перевірки стану скролу. Виправляється перевіркою recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE перед показом. Так ми вирішили проблему в 15 проєктах.

Друга помилка — відсутність візуального виділення при утриманні. itemView повинен мати android:background="?attr/selectableItemBackground" для коректного ripple-ефекту. Без цього 40% користувачів не розуміють, що меню доступне.

Третя — іконки не відображаються в PopupMenu. Викличте setForceShowIcon(true) (потрібен API 28). Для старих версій — кастомний адаптер або перехід на BottomSheet.

Четверта — меню закривається при повороті екрану. Рішення: зберігати стан у savedInstanceState або ViewModel. У нас так було в 5 проєктах — закладка виправляє ситуацію.

Покроковий процес реалізації

  1. Аналіз вимог: визначити, які дії потрібні та на яких елементах.
  2. Вибір компонента: PopupMenu для простих списків, BottomSheet для складних форм.
  3. Інтеграція жесту: прив'язати OnLongClickListener до елемента або використати combinedClickable в Compose.
  4. Тестування на пристроях: перевірити на різних розмірах екрану та версіях Android.
  5. Оптимізація: налаштувати тривалість натискання, додати хаптик-фідбек.
Підхід Коли використовувати Час реалізації
View + PopupMenu Прості списки, підтримка старих API 1-2 години
View + BottomSheet Складні меню, Material Design 3-4 години
Compose Нові проєкти, швидка розробка 2-3 години
Розширений приклад для Compose з анімацією
@Composable fun LongPressMenuItem(item: Item, onDelete: (Item) -> Unit, onShare: (Item) -> Unit) { var showMenu by remember { mutableStateOf(false) } Box( modifier = Modifier .combinedClickable( onClick = { /* відкрити деталі */ }, onLongClick = { showMenu = true } ) .padding(16.dp) ) { Text(text = item.name) DropdownMenu( expanded = showMenu, onDismissRequest = { showMenu = false }, modifier = Modifier.background(MaterialTheme.colorScheme.surface) ) { DropdownMenuItem( text = { Text("Видалити") }, onClick = { onDelete(item); showMenu = false }, leadingIcon = { Icon(Icons.Default.Delete, contentDescription = null) } ) DropdownMenuItem( text = { Text("Поділитися") }, onClick = { onShare(item); showMenu = false }, leadingIcon = { Icon(Icons.Default.Share, contentDescription = null) } ) } } } 

Що входить у роботу

При замовленні послуги ви отримуєте:

  • Вихідний код інтеграції з коментарями
  • Документацію з налаштування та кастомізації
  • Тестування на 10+ пристроях з різними версіями Android (5.0–14)
  • 2 тижні технічної підтримки після здачі

Гарантуємо працездатність на всіх цільових пристроях та надаємо сертифікат на виконані роботи. Оцінюємо проєкт за 1 день — напишіть нам, і ми підберемо оптимальне рішення для вашого додатку. Отримайте консультацію щодо інтеграції long press меню вже сьогодні.

Ми використовуємо RecyclerView та Jetpack Compose в кожному проєкті, що забезпечує сумісність із сучасними стандартами.