You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose中嵌套DropdownMenu的ExposedDropdownMenu层级与行为异常

问题原因与解决方案

你的问题核心在于Jetpack Compose中的DropdownMenu和ExposedDropdownMenu都是基于Popup实现的,Popup默认会被挂载到Window的DecorView层级,而非父布局内部。这就导致嵌套的两个Popup(父DropdownMenu和子ExposedDropdownMenu)处于同一层级,而非嵌套关系,调整zIndex无法改变它们的渲染顺序——先创建的Popup会被后创建的覆盖,或者反过来,取决于系统的Popup管理逻辑,最终出现子菜单被父菜单遮挡、锚点脱离的问题。

ExposedDropdownMenu并非完全不能嵌套,但由于Popup的特性,这种嵌套会引发层级冲突,不推荐这么做。下面是两种可行的解决思路:

方案一:拆分菜单,避免Popup嵌套

点击父DropdownMenu中的"字体选择"项时,先关闭父菜单,再打开ExposedDropdownMenu,将其锚定到设置按钮或对应的位置:

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("nstlqify") },
            colors = TopAppBarDefaults.topAppBarColors(
                containerColor = MaterialTheme.colorScheme.primary,
                titleContentColor = MaterialTheme.colorScheme.onPrimary
            ),
            scrollBehavior = scrollBehavior,
            actions = {
                val settingsButtonOffset = remember { mutableStateOf(IntOffset.Zero) }
                IconButton(onClick = {
                    showMenu = !showMenu
                }) {
                    Icon(
                        imageVector = Icons.Default.Settings,
                        contentDescription = "Settings",
                        tint = MaterialTheme.colorScheme.onPrimary,
                        modifier = Modifier.onGloballyPositioned {
                            settingsButtonOffset.value = it.localToWindow(IntOffset.Zero)
                        }
                    )
                }

                // 父设置菜单
                DropdownMenu(
                    expanded = showMenu,
                    onDismissRequest = { showMenu = false }
                ) {
                    DropdownMenuItem(
                        text = { Text("选择字体") },
                        onClick = {
                            showMenu = false
                            showFontSelection = true
                        }
                    )
                    // 字号调整项保留在父菜单
                    Box(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
                        Row {
                            Text(text = "Font Size")
                            IconButton(onClick = { fontSizeUpdated += 1 }) {
                                Icon(
                                    imageVector = Icons.Default.KeyboardArrowUp,
                                    contentDescription = "Increase",
                                    tint = MaterialTheme.colorScheme.onPrimary
                                )
                            }
                            Text(text = fontSizeUpdated.toString())
                            IconButton(onClick = { fontSizeUpdated -= 1 }) {
                                Icon(
                                    imageVector = Icons.Default.KeyboardArrowDown,
                                    contentDescription = "Decrease",
                                    tint = MaterialTheme.colorScheme.onPrimary
                                )
                            }
                        }
                    }
                }

                // 字体选择菜单,锚定到设置按钮
                ExposedDropdownMenuBox(
                    expanded = showFontSelection,
                    onExpandedChange = { showFontSelection = !showFontSelection },
                    modifier = Modifier.offset {
                        settingsButtonOffset.value.copy(y = settingsButtonOffset.value.y + 56) // 调整偏移匹配按钮位置
                    }
                ) {
                    TextField(
                        readOnly = true,
                        value = selectedFontName,
                        onValueChange = {},
                        label = { Text("Choose Font") },
                        modifier = Modifier.menuAnchor()
                    )

                    ExposedDropdownMenu(
                        expanded = showFontSelection,
                        onDismissRequest = { showFontSelection = false }
                    ) {
                        DropdownMenuItem(
                            text = { Text("JNN Regular") },
                            onClick = {
                                selectedFontName = "JNN Regular"
                                showFontSelection = false
                            }
                        )
                        DropdownMenuItem(
                            text = { Text("JNN Kasheeda") },
                            onClick = {
                                selectedFontName = "JNN Kasheeda"
                                showFontSelection = false
                            }
                        )
                    }
                }
            }
        )
    },
    modifier = Modifier.fillMaxSize()
)

方案二:用普通布局替代Popup,实现嵌套展开

放弃使用ExposedDropdownMenu的Popup特性,改用状态控制的Column来显示字体列表,这样所有内容都在父DropdownMenu内部,层级完全可控:

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("nstlqify") },
            colors = TopAppBarDefaults.topAppBarColors(
                containerColor = MaterialTheme.colorScheme.primary,
                titleContentColor = MaterialTheme.colorScheme.onPrimary
            ),
            scrollBehavior = scrollBehavior,
            actions = {
                IconButton(onClick = { showMenu = !showMenu }) {
                    Icon(
                        imageVector = Icons.Default.Settings,
                        contentDescription = "Settings",
                        tint = MaterialTheme.colorScheme.onPrimary
                    )
                }

                DropdownMenu(
                    expanded = showMenu,
                    onDismissRequest = { showMenu = false }
                ) {
                    // 字体选择区域,用状态控制展开/收起
                    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
                        TextField(
                            readOnly = true,
                            value = selectedFontName,
                            onValueChange = {},
                            label = { Text("Choose Font") },
                            trailingIcon = {
                                Icon(
                                    imageVector = if (showFontSelection) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
                                    contentDescription = if (showFontSelection) "收起" else "展开"
                                )
                            },
                            modifier = Modifier.fillMaxWidth().clickable { showFontSelection = !showFontSelection }
                        )

                        // 展开时显示字体列表
                        if (showFontSelection) {
                            Column(modifier = Modifier.fillMaxWidth()) {
                                TextButton(
                                    onClick = {
                                        selectedFontName = "JNN Regular"
                                        showFontSelection = false
                                    },
                                    modifier = Modifier.fillMaxWidth()
                                ) {
                                    Text("JNN Regular")
                                }
                                TextButton(
                                    onClick = {
                                        selectedFontName = "JNN Kasheeda"
                                        showFontSelection = false
                                    },
                                    modifier = Modifier.fillMaxWidth()
                                ) {
                                    Text("JNN Kasheeda")
                                }
                            }
                        }
                    }

                    // 字号调整项
                    Box(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
                        Row {
                            Text(text = "Font Size")
                            IconButton(onClick = { fontSizeUpdated += 1 }) {
                                Icon(
                                    imageVector = Icons.Default.KeyboardArrowUp,
                                    contentDescription = "Increase",
                                    tint = MaterialTheme.colorScheme.onPrimary
                                )
                            }
                            Text(text = fontSizeUpdated.toString())
                            IconButton(onClick = { fontSizeUpdated -= 1 }) {
                                Icon(
                                    imageVector = Icons.Default.KeyboardArrowDown,
                                    contentDescription = "Decrease",
                                    tint = MaterialTheme.colorScheme.onPrimary
                                )
                            }
                        }
                    }
                }
            }
        )
    },
    modifier = Modifier.fillMaxSize()
)

两种方案各有优劣:方案一保留了ExposedDropdownMenu的原生交互体验,方案二更贴合嵌套菜单的视觉层级,你可以根据需求选择。

内容的提问来源于stack exchange,提问作者M. Daniyal Malik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 01:17:03