Jetpack Compose中双TextField通用下拉菜单实现及聚焦问题解决
解决Jetpack Compose双TextField下拉菜单焦点跳转问题
在Jetpack Compose中实现双TextField联动下拉菜单时,点击第二个TextField打开下拉菜单后,焦点会自动跳转到第一个TextField,不符合预期需求。需求是:
- 两个TextField分别对应
Dummy数据类的a、b字段 - 根据输入过滤下拉列表:第一个TextField有输入则按
a字段过滤,否则按b字段过滤 - 下拉项以
a | b的格式展示
核心问题是ExposedDropdownMenuBox默认会将焦点绑定到单个锚点组件,而这里用两个TextField作为锚点导致焦点管理冲突。以下是修复后的代码及关键说明:
data class Dummy( val a: String, val b: String, ) @OptIn(ExperimentalMaterial3Api::class) @Composable fun DoubleTextFieldDropDown( selectedDummy: Dummy?, onSelectedChange: (Dummy?) -> Unit, list: List<Dummy> ) { var exposed by remember { mutableStateOf(false) } var textA by remember(selectedDummy) { mutableStateOf(selectedDummy?.a ?: "") } var textB by remember(selectedDummy) { mutableStateOf(selectedDummy?.b ?: "") } // 为两个TextField创建焦点请求器,跟踪当前聚焦的输入框 val focusRequesterA = remember { FocusRequester() } val focusRequesterB = remember { FocusRequester() } val currentFocus = remember { mutableStateOf<FocusRequester?>(null) } // 优化过滤逻辑,明确依赖项确保正确更新 val filtered by remember(textA, textB, list) { derivedStateOf { when { textA.isNotEmpty() -> list.filter { it.a.contains(textA) } textB.isNotEmpty() -> list.filter { it.b.contains(textB) } else -> list } } } ExposedDropdownMenuBox( expanded = exposed, onExpandedChange = { newExpanded -> exposed = newExpanded // 展开下拉时恢复之前的焦点 if (newExpanded) { currentFocus.value?.requestFocus() } }, ) { Row( modifier = Modifier.menuAnchor(MenuAnchorType.SecondaryEditable) ) { OutlinedTextField( value = textA, onValueChange = { textA = it }, modifier = Modifier .weight(1F) .focusRequester(focusRequesterA) .onFocusChanged { state -> if (state.isFocused) { currentFocus.value = focusRequesterA // 点击输入框时自动展开下拉 if (!exposed) exposed = true } }, label = { Text("Text A") }, keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.None) ) OutlinedTextField( value = textB, onValueChange = { textB = it }, modifier = Modifier .weight(1F) .focusRequester(focusRequesterB) .onFocusChanged { state -> if (state.isFocused) { currentFocus.value = focusRequesterB // 点击输入框时自动展开下拉 if (!exposed) exposed = true } }, label = { Text("Text B") }, keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.None) ) } ExposedDropdownMenu( expanded = exposed, onDismissRequest = { exposed = false } ) { filtered.forEach { dummy -> DropdownMenuItem( onClick = { onSelectedChange(dummy) textA = dummy.a textB = dummy.b exposed = false // 选择选项后恢复之前的焦点 currentFocus.value?.requestFocus() }, text = { Text("${dummy.a} | ${dummy.b}") }, ) } } } }
关键修复点
- 手动焦点跟踪:为每个TextField创建
FocusRequester,通过onFocusChanged记录当前聚焦的输入框,避免默认焦点自动跳转。 - 调整菜单锚点类型:使用
MenuAnchorType.SecondaryEditable替代默认的PrimaryEditable,取消强制绑定单个锚点的焦点行为。 - 焦点恢复逻辑:在下拉菜单展开、选项选择完成后,将焦点恢复到之前选中的TextField。
- 禁用IME干扰:设置
imeAction = ImeAction.None,避免键盘操作触发不必要的焦点切换。 - 过滤逻辑优化:明确
derivedStateOf的依赖项,确保输入变化时过滤结果实时更新。
内容的提问来源于stack exchange,提问作者Surendra
相关产品推荐
相关产品推荐

