Context Menu in Compose
Long-press interactions are crucial for rich UI experiences. Let's explore three patterns: custom dropdown menus, bottom sheets, and selection mode.
Pattern 1: Long Press Dropdown Menu
Use combinedClickable with LongClickLabel for accessible long-press with context actions:
var showMenu by remember { mutableStateOf(false) }
var selectedIndex by remember { mutableStateOf(-1) }
Box(
modifier = Modifier
.combinedClickable(
onClick = { /* handle click */ },
onLongClick = {
selectedIndex = index
showMenu = true
},
longClickLabel = "Show item actions"
)
) {
Text(item.title)
DropdownMenu(
expanded = showMenu && selectedIndex == index,
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(
text = { Text("Edit") },
onClick = {
onEdit(item)
showMenu = false
}
)
Divider()
DropdownMenuItem(
text = { Text("Share") },
onClick = {
onShare(item)
showMenu = false
}
)
Divider()
DropdownMenuItem(
text = { Text("Delete") },
onClick = {
onDelete(item)
showMenu = false
}
)
}
}
Pattern 2: Modal Bottom Sheet with Header
For mobile-friendly menus on large screens:
var showBottomSheet by remember { mutableStateOf(false) }
if (showBottomSheet) {
ModalBottomSheet(onDismissRequest = { showBottomSheet = false }) {
Column {
// Header
Text(
"${item.title} — Actions",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
fontWeight = FontWeight.Bold
)
Divider()
// Actions
ListItem(
headlineContent = { Text("Edit") },
modifier = Modifier.clickable {
onEdit(item)
showBottomSheet = false
}
)
ListItem(
headlineContent = { Text("Share") },
modifier = Modifier.clickable {
onShare(item)
showBottomSheet = false
}
)
ListItem(
headlineContent = { Text("Delete", color = Color.Red) },
modifier = Modifier.clickable {
onDelete(item)
showBottomSheet = false
}
)
}
}
}
Pattern 3: Selection Mode with TopAppBar
Multi-select with toolbar:
var selectionMode by remember { mutableStateOf(false) }
val selectedItems = remember { mutableStateListOf<Item>() }
Scaffold(
topBar = {
if (selectionMode) {
TopAppBar(
title = { Text("${selectedItems.size} selected") },
navigationIcon = {
IconButton(onClick = {
selectionMode = false
selectedItems.clear()
}) {
Icon(Icons.Default.Close, "Cancel")
}
},
actions = {
IconButton(onClick = { /* delete all */ }) {
Icon(Icons.Default.Delete, "Delete")
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary
)
)
} else {
TopAppBar(title = { Text("Items") })
}
}
) { padding ->
LazyColumn(contentPadding = padding) {
items(itemList) { item ->
Row(
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
onClick = {
if (selectionMode) {
if (item in selectedItems) {
selectedItems.remove(item)
} else {
selectedItems.add(item)
}
}
},
onLongClick = {
if (!selectionMode) {
selectionMode = true
selectedItems.add(item)
}
}
)
.background(
if (item in selectedItems)
MaterialTheme.colorScheme.surfaceVariant
else
Color.Transparent
),
verticalAlignment = Alignment.CenterVertically
) {
if (selectionMode) {
Checkbox(
checked = item in selectedItems,
onCheckedChange = {
if (it) selectedItems.add(item)
else selectedItems.remove(item)
},
modifier = Modifier.padding(8.dp)
)
}
Text(item.title, modifier = Modifier.weight(1f).padding(8.dp))
}
}
}
}
Comparison Table
| Pattern | Use Case | Mobile | Desktop | Accessibility |
|---|---|---|---|---|
| Dropdown Menu | Single item, quick actions | ✓ | ✓✓ | Good |
| Bottom Sheet | Large action sets | ✓✓ | ✓ | Excellent |
| Selection Mode | Bulk operations | ✓✓ | ✓ | Good |
Ready to build Android apps faster? Download our 8 Android app templates.
Top comments (0)