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
相关产品推荐
相关产品推荐

