Implementing Long Press Menu for Android Applications
Long press is a gesture users intuitively expect: holding down on a list item opens contextual actions. In practice, implementation breaks in two places: incorrect gesture recognition during fast RecyclerView scrolling and visually unclear menu display.
Implementation Options
PopupMenu — the simplest approach for lists with fixed actions. It attaches to a View via an anchor and inflates from an XML resource:
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
}
Issue: PopupMenu appears at a corner of the View without positional animation. On small screens or in the lower part of a list, the menu goes off-screen. From API 28 onwards, setForceShowIcon(true) enables icons in PopupMenu—without this flag, icons from menu.xml are simply ignored.
ContextMenu via registerForContextMenu() — a deprecated approach, tied to Activity and works poorly with RecyclerView.
BottomSheetDialog — the preferred choice for modern Material Design 3 applications. Visually cleaner, independent of element position on screen, easily extensible. Implemented via MaterialAlertDialogBuilder or a custom BottomSheetDialogFragment.
Scrolling and Gesture Issues
In RecyclerView, OnLongClickListener competes with ItemTouchHelper if used for swiping. If ItemTouchHelper consumes the event first, onLongClick doesn't fire. Solution: explicitly handle MotionEvent.ACTION_DOWN and ACTION_CANCEL in OnItemTouchListener, delegating control after a timeout based on ViewConfiguration.getLongPressTimeout().
Another consideration: haptic feedback on long press. view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) is called manually within onLongClick because the system doesn't guarantee it automatically for custom views.
Compose
In Jetpack Compose, use combinedClickable:
Box(
modifier = Modifier.combinedClickable(
onClick = { onClick(item) },
onLongClick = { showMenu = true }
)
) {
// item content
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(text = { Text("Delete") }, onClick = { onDelete(item); showMenu = false })
DropdownMenuItem(text = { Text("Share") }, onClick = { onShare(item); showMenu = false })
}
}
DropdownMenu positions itself automatically relative to the parent Box—the platform handles off-screen boundary issues.
Common Mistakes
Menu opens during scroll. Cause: OnLongClickListener on itemView without checking scroll state. Fix: verify recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE before showing the menu.
No visual feedback while holding. The itemView should have android:background="?attr/selectableItemBackground" for correct ripple effect on long press.
Implementation complexity ranges from a few hours (simple PopupMenu) to 1–2 days (custom BottomSheet with animations and multi-select). Cost is calculated individually.







