Search code examples
androiddrop-down-menuandroid-jetpack-compose-material3

Android Compose DropdownMenu with android:checkableBehavior="single" similar to xml menu


I am migrating my android xml layouts to compose and have hit an issue when trying to implement a drop down menu in compose that supports the following xml version of a dropdown menu

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:title="@string/my_title" android:enabled="false" />
    <group android:id="@+id/my_group" android:checkableBehavior="single">
        <item
            android:id="@+id/my_allocate"
            android:icon="@drawable/my_allocate"
            android:title="@string/context_menu_allocate" />
        <item
            android:id="@+id/my_delete"
            android:icon="@drawable/my_delete"
            android:title="@string/context_menu_delete" />
        <item
            android:id="@+id/my_context"
            android:icon="@drawable/my_context"
            android:title="@string/context_menu" />
    </group>
</menu>

is this possible in compose? why doesnt androidx.compose.material3.DropdownMenu & androidx.compose.material3.DropdownMenuItem support dading groups


Solution

  • You can do something like this:

    data class MenuItem(
        val name: String,
        val icon: ImageVector,
        val selectedIcon: ImageVector,
        val selected: Boolean = false
    )
    
    @Composable
    fun Menu() {
        val menuItems = remember {
            mutableStateListOf(
                listOf(
                    MenuItem(
                        name = "Home",
                        icon = Icons.Outlined.Home,
                        selectedIcon = Icons.Filled.Home,
                        selected = false
                    ),
                    MenuItem(
                        name = "Favorites",
                        icon = Icons.Outlined.FavoriteBorder,
                        selectedIcon = Icons.Filled.Favorite,
                        selected = false
                    )
                ),
                listOf(
                    MenuItem(
                        name = "Home",
                        icon = Icons.Outlined.Home,
                        selectedIcon = Icons.Filled.Home,
                        selected = false
                    ),
                    MenuItem(
                        name = "Favorites",
                        icon = Icons.Outlined.FavoriteBorder,
                        selectedIcon = Icons.Filled.Favorite,
                        selected = false
                    )
                )
            )
        }
    
        val onClick = { groupId: Int, selectedItemIndex: Int ->
            val newGroup: MutableList<MenuItem> = mutableListOf()
            menuItems[groupId].forEachIndexed { index, item ->
                newGroup.add(item.copy(selected = index == selectedItemIndex))
            }
            menuItems[groupId] = newGroup
        }
    
        DropdownMenu(expanded = true, onDismissRequest = { }) {
            menuItems.forEachIndexed { groupIndex, group ->
                group.forEachIndexed { index, item ->
                    DropdownMenuItem(
                        text = { Text(text = item.name) },
                        onClick = { onClick(groupIndex, index) },
                        leadingIcon = {
                            Icon(
                                imageVector = if (item.selected) item.selectedIcon else item.icon,
                                contentDescription = item.name
                            )
                        },
                        trailingIcon = {
                            RadioButton(
                                selected = item.selected,
                                onClick = { onClick(groupIndex, index) }
                            )
                        }
                    )
                }
                if (groupIndex < menuItems.size - 1) {
                    HorizontalDivider()
                }
            }
        }
    }
    

    This is the result:

    enter image description here

    This code creates the menu view with the expected behavior. You can use the same logic to build a side menu or else.