Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Jetpack compose ActionMenu
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
// Kind of equivalent to a menu XML entry, except for the onClick lambda
data class ActionItemSpec(
val name: String,
val icon: ImageVector,
val visibility: ActionItemMode = ActionItemMode.IF_ROOM,
val onClick: () -> Unit,
)
// Whether to show the action item as an icon or not (or if room)
enum class ActionItemMode {
ALWAYS_SHOW, IF_ROOM, NEVER_SHOW
}
@Preview
@Composable
fun PreviewActionMenu() {
val items = listOf(
ActionItemSpec("Call", Icons.Default.Call, ActionItemMode.ALWAYS_SHOW) {},
ActionItemSpec("Send", Icons.Default.Send, ActionItemMode.IF_ROOM) {},
ActionItemSpec("Email", Icons.Default.Email, ActionItemMode.IF_ROOM) {},
ActionItemSpec("Delete", Icons.Default.Delete, ActionItemMode.IF_ROOM) {},
)
TopAppBar(
title = { Text("App bar") },
navigationIcon = {
IconButton(onClick = {}) {
Icon(Icons.Default.Menu, "Menu")
}
},
actions = {
ActionMenu(items, defaultIconSpace = 3)
}
)
}
@Composable
fun ActionMenu(
items: List<ActionItemSpec>,
defaultIconSpace: Int = 3, // includes overflow menu
menuExpanded: MutableState<Boolean> = remember { mutableStateOf(false) }
) {
// decide how many ifRoom icons to show as actions
val (actionItems, overflowItems) = remember(items, defaultIconSpace) {
separateIntoActionAndOverflow(items, defaultIconSpace)
}
with(RowScope) {
for (item in actionItems) {
IconButton(onClick = item.onClick) {
Icon(item.icon, item.name)
}
}
if (overflowItems.isNotEmpty()) {
IconButton(onClick = { menuExpanded.value = true }) {
Icon(Icons.Default.MoreVert, "More actions")
}
DropdownMenu(
expanded = menuExpanded.value,
onDismissRequest = { menuExpanded.value = false }
) {
for (item in overflowItems) {
DropdownMenuItem(onClick = item.onClick) {
//Icon(item.icon, item.name) just have text in the overflow menu
Text(item.name)
}
}
}
}
}
}
private fun separateIntoActionAndOverflow(
items: List<ActionItemSpec>,
defaultIconSpace: Int
): Pair<List<ActionItemSpec>, List<ActionItemSpec>> {
var (alwaysCount, neverCount, ifRoomCount) = Triple(0, 0, 0)
for (item in items) {
when (item.visibility) {
ActionItemMode.ALWAYS_SHOW -> alwaysCount++
ActionItemMode.NEVER_SHOW -> neverCount++
ActionItemMode.IF_ROOM -> ifRoomCount++
}
}
val needsOverflow = alwaysCount + ifRoomCount > defaultIconSpace || neverCount > 0
val actionIconSpace = defaultIconSpace - (if (needsOverflow) 1 else 0)
val actionItems = ArrayList<ActionItemSpec>()
val overflowItems = ArrayList<ActionItemSpec>()
var ifRoomsToDisplay = actionIconSpace - alwaysCount
for (item in items) {
when (item.visibility) {
ActionItemMode.ALWAYS_SHOW -> {
actionItems.add(item)
}
ActionItemMode.NEVER_SHOW -> {
overflowItems.add(item)
}
ActionItemMode.IF_ROOM -> {
if (ifRoomsToDisplay > 0) {
actionItems.add(item)
ifRoomsToDisplay--
} else {
overflowItems.add(item)
}
}
}
}
return Pair(actionItems, overflowItems)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment