在 Jetpack Compose 中创建 "nested" 菜单的更好或更简单的方法是什么?

What is the better or easier way to create "nested" menus in Jetpack Compose?

所以在 XML 中,您可以构建菜单项并像这样嵌套它们。


但在 Jetpack Compose 中,我无法弄清楚它是如何工作的。 我已经从 here 阅读并构建了一个简单的下拉菜单。但是尝试在 jetpack compose 中做与 XML 相同的事情并没有多大意义。菜单是单独和独立创建的。我正在寻找比这更简单更好的东西。

我也在想这个问题。 目前,我是这样组成这个嵌套菜单的:

@Composable
fun ExpandableDropdownItem(
    text: String,
    dropDownItems: @Composable () -> Unit
) {
    var expanded by remember {
        mutableStateOf(false)
    }
    DropdownMenuItem(onClick = {
        expanded = true
    }) {
        Text(text = text)
    }
    DropdownMenu(
        expanded = expanded,
        onDismissRequest = { expanded = false }
    ) {
        dropDownItems()
    }
}

这可以与其他 DropdownItems 一起使用。 这不是最好的解决方案:嵌套菜单的位置很奇怪。

这样的怎么样?

为您的主菜单创建一个可组合项,为您的嵌套菜单创建一个。

可组合主菜单

@Composable
fun MainMenu(
    menuSelection: MutableState<MenuSelection>,
    expandedMain: MutableState<Boolean>,
    expandedNested: MutableState<Boolean>
) {
    DropdownMenu(
        expanded = expandedMain.value,
        onDismissRequest = { expandedMain.value = false },
    ) {
        DropdownMenuItem(
            onClick = {
                expandedMain.value = false // hide main menu
                expandedNested.value = true // show nested menu
                menuSelection.value = MenuSelection.NESTED
            }
        ) {
            Text("Nested Options \u25B6")
        }

        Divider()

        DropdownMenuItem(
            onClick = {
                // close main menu
                expandedMain.value = false
                menuSelection.value = MenuSelection.SETTINGS
            }
        ) {
            Text("Settings")
        }

        Divider()

        DropdownMenuItem(
            onClick = {
                // close main menu
                expandedMain.value = false
                menuSelection.value = MenuSelection.ABOUT
            }
        ) {
            Text("About")
        }
    }
}

嵌套菜单可组合

@Composable
fun NestedMenu(
    expandedNested: MutableState<Boolean>,
    nestedMenuSelection: MutableState<NestedMenuSelection>
) {
    DropdownMenu(
        expanded = expandedNested.value,
        onDismissRequest = { expandedNested.value = false }
    ) {
        DropdownMenuItem(
            onClick = {
                // close nested menu
                expandedNested.value = false
                nestedMenuSelection.value = NestedMenuSelection.FIRST
            }
        ) {
            Text("First")
        }
        DropdownMenuItem(
            onClick = {
                // close nested menu
                expandedNested.value = false
                nestedMenuSelection.value = NestedMenuSelection.SECOND
            }
        ) {
            Text("Second")
        }
    }
}

然后将它们放在可组合的主下拉菜单中。

顶部栏可组合

@Composable
fun TopAppBarDropdownMenu(
    menuSelection: MutableState<MenuSelection>,
    nestedMenuSelection: MutableState<NestedMenuSelection>
) {

    val expandedMain = remember { mutableStateOf(false) }
    val expandedNested = remember { mutableStateOf(false) }

    // Three Dot icon
    Box(
        Modifier
            .wrapContentSize(Alignment.TopEnd)
    ) {
        IconButton(
            onClick = {
                // Expand the main menu on three dots icon click
                // and hide the nested menu. 
                expandedMain.value = true
                expandedNested.value = false
            }
        ) {
            Icon(
                Icons.Filled.MoreVert,
                contentDescription = "More Menu"
            )
        }
    }

    MainMenu(
        menuSelection = menuSelection,
        expandedMain = expandedMain,
        expandedNested = expandedNested
    )
    NestedMenu(
        expandedNested = expandedNested,
        nestedMenuSelection = nestedMenuSelection
    )

}

TopAppBarDropdownMenumenuSelectionnestedMenuSelection 参数将允许在 TopAppBarDropdownMenu 主机中执行操作,具体取决于从菜单激活的状态。像这样:

@Composable
fun MainScreen(
    /* some params */
) {
    
    val menuSelection = remember { mutableStateOf(MenuSelection.NONE) }
    val nestedMenuSelection = remember { mutableStateOf(NestedMenuSelection.DEFAULT) }
    
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = stringResource(R.string.app_name)) },
                actions = {
                    TopAppBarDropdownMenu(
                        menuSelection = menuSelection,
                        nestedMenuSelection= nestedMenuSelection
                    )
                }
            )
        }
    )
}

不确定这是“正确”的方法,但对我有用。

您可以通过创建具有更多参数的 NestedMenu 可组合项来更轻松地实例化嵌套菜单,这将使其更易于重用。请记住 Material Design guidelines recommend using Cascading Menus only on desktop.

我制作了一个用于排序的嵌套菜单变体,使用 AnimatedVisibility 似乎效果很好。

展开时,它将顶级菜单的可见性切换到子菜单,如果需要甚至可以添加后退按钮,但更多级别可能会与开关混淆。

@Composable
fun NestedSortMenu(
    onSortClick: (SortOrder) -> Unit
) {
    var expanded by remember { mutableStateOf(false) }
    var orderType: OrderType by remember { mutableStateOf(OrderType.Ascending) }

    AnimatedVisibility(visible = !expanded) {
        Column {
            DropdownMenuItem(onClick = {
                orderType = OrderType.Ascending
                expanded = !expanded
            }) {
                Text(text = stringResource(R.string.ascending))
                Icon(
                    Icons.Filled.NavigateNext,
                    contentDescription = stringResource(R.string.ascending),
                    modifier = Modifier.size(20.dp)
                )
            }
            DropdownMenuItem(onClick = {
                orderType = OrderType.Descending
                expanded = !expanded
            }) {
                Text(text = stringResource(R.string.descending))
                Icon(
                    Icons.Filled.NavigateNext,
                    contentDescription = stringResource(R.string.descending),
                    modifier = Modifier.size(20.dp)
                )
            }
        }

    }
    AnimatedVisibility(visible = expanded) {
        Column {
            DropdownMenuItem(onClick = {
                expanded = !expanded
                onSortClick(SortOrder.Title(orderType))
            }) {
                Text(text = stringResource(R.string.title_menu))
            }
            DropdownMenuItem(onClick = {
                expanded = !expanded
                onSortClick(SortOrder.Format(orderType))
            }) {
                Text(text = stringResource(R.string.format_menu))
            }
            DropdownMenuItem(onClick = {
                expanded = !expanded
                onSortClick(SortOrder.DateAndTime(orderType))
            }) {
                Text(text = stringResource(R.string.date_and_time))
            }
        }
    }
}