下拉菜单 DropMenu
2026/1/31大约 3 分钟
下拉菜单 DropMenu
本笔记整理 Jetpack Compose 中常用的下拉菜单相关 API、使用模式与示例,包括 DropdownMenu、DropdownMenuItem、ExposedDropdownMenuBox(带 TextField 的下拉选择)、以及 PopupProperties、位置与可访问性等注意事项。
核心组件与概念
DropdownMenu(androidx.compose.material.DropdownMenu):基于Popup的菜单控件,通常以某个锚点(Anchor)为相对位置弹出。DropdownMenuItem:菜单项,通常包含文本、图标与点击行为。ExposedDropdownMenuBox(Material/Material3):用于实现带输入框的下拉选择(exposed dropdown),与ExposedDropdownMenuDefaults配合使用以获得一致样式。PopupProperties:用于控制弹出窗口行为(是否 focusable、是否 dismissOnBackPress 等)。
常用参数
expanded: Boolean:是否展开菜单(由上层状态控制)。onDismissRequest: () -> Unit:点击外部或退出时的回调,用于关闭菜单。offset: DpOffset:相对于锚点的偏移。properties: PopupProperties:控制 popup 行为。enabled: Boolean(在DropdownMenuItem上):是否可点击。contentPadding/leadingIcon/trailingIcon:用于自定义菜单项内容。
状态管理(state hoisting)
- 推荐将
expanded状态提升到父组件或 ViewModel:
var expanded by remember { mutableStateOf(false) }
IconButton(onClick = { expanded = true }) { Icon(Icons.Default.MoreVert, null) }
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { /* items */ }基本示例
@Composable
fun SimpleDropdown() {
var expanded by remember { mutableStateOf(false) }
Box {
IconButton(onClick = { expanded = true }) {
Icon(Icons.Default.MoreVert, contentDescription = "更多")
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(onClick = { /* 操作1 */ expanded = false }) {
Text("操作 1")
}
DropdownMenuItem(onClick = { /* 操作2 */ expanded = false }) {
Text("操作 2")
}
}
}
}带图标与禁用项
DropdownMenuItem(onClick = { /*...*/ }, enabled = true) {
Icon(Icons.Default.Share, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text("分享")
}
DropdownMenuItem(onClick = {}, enabled = false) {
Text("该项不可用")
}Exposed Dropdown(带 TextField 的选择)
Exposed dropdown 常用于选择器场景:
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ExposedDropdownSample(options: List<String>) {
var expanded by remember { mutableStateOf(false) }
var selectedText by remember { mutableStateOf("") }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded }
) {
TextField(
value = selectedText,
onValueChange = { selectedText = it },
readOnly = true,
label = { Text("请选择") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier.fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
options.forEach { selectionOption ->
DropdownMenuItem(onClick = {
selectedText = selectionOption
expanded = false
}) {
Text(text = selectionOption)
}
}
}
}
}注意:ExposedDropdownMenuBox/ExposedDropdownMenu 在 Material3 中也存在对应实现,API 名称与样式可能略有变化。
自定义位置与 PopupProperties
- 控制菜单是否可获取焦点、是否拦截后退键、是否可在输入法上方显示等:
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
offset = DpOffset(x = 0.dp, y = 8.dp),
properties = PopupProperties(focusable = true, dismissOnBackPress = true)
) { /* ... */ }在复杂场景中的用法
- 菜单项可以放入复杂布局(如包含二级说明、checkbox、radio 等)。
- 对于大量选项,考虑在
DropdownMenu内部放置LazyColumn限制高度并支持滚动。
示例:菜单内滚动
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
Box(modifier = Modifier.heightIn(max = 240.dp)) {
LazyColumn { items(largeList) { item -> DropdownMenuItem(onClick = { /*...*/ }) { Text(item) } } }
}
}可访问性
- 提供
contentDescription给触发按钮,菜单项使用清晰文本。 DropdownMenu的onDismissRequest应确保当用户点击外部或按返回键时关闭菜单。
键盘与焦点行为
PopupProperties(focusable = true)能让菜单获取焦点并响应键盘事件。- 在表单或键盘可见时,注意菜单的显示位置与输入法遮挡问题。
Material3 与主题
- 在 Material3 中使用
ExposedDropdownMenuDefaults或DropdownMenu的 Material3 版本,其视觉样式(elevation、padding、shape、colors)有所不同。
性能与最佳实践
- 状态提升:
expanded、选中项等状态应提升到父组件或 ViewModel。 - 避免在菜单打开时创建大量对象;使用
remember缓存静态数据与 painter。
小结(速查)
- 组件:
DropdownMenu、DropdownMenuItem、ExposedDropdownMenuBox、ExposedDropdownMenu - 关键参数:
expanded、onDismissRequest、offset、properties、enabled - 模式:锚点触发(Icon/Button)、ExposedDropdown(TextField + 下拉)、菜单内滚动(LazyColumn)
- 可访问性:确保触发控件与菜单项有清晰文本,使用
toggleable/selectable/role增强语义
如果你想,我可以把这些示例拆成带 @Preview 的 Kotlin 示例文件并放入仓库样例模块。