Jetpack Compose 弹出菜单

21
我正在尝试使用Jetpack Compose重写我的项目UI。有没有想法在Android中使用Jetpack Compose添加弹出菜单?就像下面这个例子一样。

like this one

我尝试使用Stack()布局实现它,但结果并不理想。
@Composable
fun LiveDataComponentList(productList: List<Product>) {
    AdapterList(data = productList) { product ->
        Stack() {
            Clickable(onClick = { PopupState.toggleOwner(product) }) {
                Card(...) {...}

            if (PopupState.owner == product) {
                Surface(color = Color.Gray,modifier = Modifier.gravity(Alignment.TopEnd) + Modifier.padding(12.dp)) {
                    Column() {
                        Text("menu 1")
                        Text("menu 2")
                        Text("menu 3")
                        Text("menu 4")
                        Text("menu 5")
                    }
                }
            }
        }
    }
}

而 PopupState 是

@Model
object PopupState
{
    var owner:Product?=null
    fun toggleOwner(item:Product)
    {
        if(owner==item)
            owner=null
        else
            owner=item
    }
}

结果是

截图

4个回答

33

您可以使用 DropdownMenu

类似于:

var expanded by remember { mutableStateOf(false) }

DropdownMenu(
    expanded = expanded,
    onDismissRequest = { expanded = false }
) {
    DropdownMenuItem(
        text = {  Text("Refresh") },
        onClick = { /* Handle refresh! */ }
    )
    DropdownMenuItem(
        text = { Text("Settings") },
        onClick = { /* Handle settings! */ }
    )
    Divider()
    DropdownMenuItem(
        text = { Text("Send Feedback") },
        onClick = { /* Handle send feedback! */ }
    )
}

它适用于M3。对于M2,您必须使用:

androidx.compose.material.DropdownMenuItem(
    onClick = { expanded = false }
) {
    Text("All Accounts")
}

enter image description here

关于该职位,如文档所述:

DropdownMenu 的行为类似于 Popup,并将使用父布局的位置来定位自己在屏幕上的位置。通常情况下,DropdownMenu 将放置在一个带有兄弟元素的Box 中,该兄弟元素将用作“锚点”。

例如:

Box(
    modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.TopStart)
){
    IconButton(onClick = { expanded = true }) {
        Icon(
            Icons.Default.MoreVert,
            contentDescription = "Localized description"
        )
    }
    DropdownMenu(
        expanded = expanded,
        onDismissRequest = { expanded = false }
    ) {
        //...
    }

1
如何在按钮或其他视图周围设置其位置? - Talha Akbar
@TalhaAkbar 请查看更新后的答案。它使用父布局的位置来定位自己在屏幕上的位置。 - Gabriele Mariotti
@GabrieleMariotti 我看不到您根据父布局的位置定位菜单的部分。 - MKiperszmid
1
@MKiperszmid 通常情况下,DropdownMenu 会放在一个 Box 中,并与充当“锚点”的同级别元素配合使用。 在此示例中,DropdownMenu 使用 IconButton 作为 *'anchor'*。 - Gabriele Mariotti

11

由于DropDownPopup已被移除,我改用DropDownMenu来实现下拉式弹窗,实现方式如下:

PopupMenu:

@Composable
fun PopupMenu(
    menuItems: List<String>,
    onClickCallbacks: List<() -> Unit>,
    showMenu: Boolean,
    onDismiss: () -> Unit,
    toggle: @Composable () -> Unit,
) {
    DropdownMenu(
        toggle = toggle,
        expanded = showMenu,
        onDismissRequest = { onDismiss() },
    ) {
        menuItems.forEachIndexed { index, item ->
            DropdownMenuItem(onClick = {
                onDismiss()
                onClickCallbacks[index]
            }) {
                Text(text = item)
            }
        }
    }
}

切换开关(长按以触发PopupMenu的对象):

@Preview
@Composable
fun Toggle() {
    var showMenu by remember { mutableStateOf(false) }

    PopupMenu(
        menuItems = listOf("Delete"),
        onClickCallbacks = listOf { println("Deleted") },
        showMenu = showMenu,
        onDismiss = { showMenu = false }) {
        Text(
            modifier = Modifier.clickable(onClick = {}, onLongClick = {
                showMenu = true
            }),
            text = "Long click here",
        )
    }
}

除了Text()之外,是否可以添加其他内容?例如添加ImageView会导致崩溃。 - Andre Thiele
1
这个怎么能够附加到一个按钮上? - Duncan Lukkenaer

2

经过一些研究,我找到了一个解决方案,关键组件是DropdownPopup

@Composable
fun LiveDataComponentList(productList: List<Product>) {
    AdapterList(data = productList) { product ->


        Clickable(onClick = { PopupState.toggleOwner(product) }) {
            Card(...) {...}
        }
        if (PopupState.owner == product) {
            DropdownPopup(dropDownAlignment = DropDownAlignment.End)
            {

                Surface(
                    shape = RoundedCornerShape(4.dp),
                    elevation = 16.dp,
                    color = Color.White,
                    modifier = Modifier.gravity(Alignment.End)+ Modifier.padding(end = 10.dp)
                )
                {
                    Column(modifier = Modifier.padding(10.dp)) {

                        MenuItem(text ="Edit", onClick = {})
                        MenuItem(text = "Delete", onClick = {})
                        MenuItem(text = "Details", onClick = {})


                    }
                }
            }
        }


    }
}

@Composable
fun MenuItem(text: String, onClick: () -> Unit) {
    Clickable(onClick = onClick, modifier = Modifier.padding(6.dp)) {
        Text(text = text, style = MaterialTheme.typography.subtitle1)

    }
}

这个解决方案可以很好地配合compose版本dev10使用。


下拉弹出窗口已于2020年8月5日被移除:androidx.compose.ui:ui-*:0.1.0-dev16移除下拉弹出窗口。(I00430)https://developer.android.com/jetpack/androidx/releases/compose-ui#0.1.0-dev16 - Avallone
1
被移除和替换为什么? - Jono
@Jonathan 可能被替换为 DropdownMenu - dumbfingers
1
我们如何使用DropdownMenu设置对齐方式? - chrgue

1

为了我的使用情况,我创建了一个图标按钮,它具有弹出菜单,并且可以在需要弹出菜单的地方使用。

@Composable
fun PopUpMenuButton(
    options: List<PopUpMenuItem>,
    action: (String) -> Unit,
    iconTint: Color = Color.Black,
    modifier: Modifier
) {

    var expanded by remember { mutableStateOf(false) }

    Column {

        Box(modifier = Modifier.size(24.dp)) {
            IconButton(onClick = {
                expanded = !expanded
            }) {
                Icon(
                    painter = painterResource(id = R.drawable.ic_dots),
                    contentDescription = null,
                    modifier = Modifier.wrapContentSize(),
                    tint = iconTint
                )
            }
        }

        Box(modifier = modifier) {
            DropdownMenu(
                expanded = expanded,
                onDismissRequest = { expanded = false },
                modifier = Modifier
                    .widthIn(min = 120.dp, max = 240.dp)
                    .background(MaterialTheme.colors.background)
            ) {
                options.forEachIndexed { _, item ->
                    DropdownMenuItem(onClick = {
                        expanded = false
                        action(item.id)
                    }) {
                        Row(
                            horizontalArrangement = Arrangement.SpaceBetween,
                            verticalAlignment = Alignment.CenterVertically,
                        ) {
                            Icon(
                                painterResource(id = item.icon),
                                contentDescription = null,
                                tint = iconTint,
                            )
                            Spacer(modifier = Modifier.width(8.dp))
                            Text(
                                text = item.label,
                                style = MaterialTheme.typography.body1,
                                overflow = TextOverflow.Ellipsis
                            )
                        }
                    }
                    if (item.hasBottomDivider) {
                        Divider()
                    }
                }
            }
        }
    }

}

然后我创建了一个简单的数据类来定义菜单项

data class PopUpMenuItem(
    val id: String,
    val label: String,
    val icon: Int,
    val hasBottomDivider: Boolean = false,
)

然后在调用方,我只需像这样使用此按钮

        PopUpMenuButton(
            modifier = Modifier.wrapContentSize(),
            options = PopMenuOptionsProvider.sectionCardMenu,
            iconTint = MaterialTheme.extendedColor.regularGray,
            action = { menuId -> onSectionMenuAction(menuId) }
        )

它可以进一步重构以使其更加可扩展,但对我来说这已经足够了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接