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

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}") },
                )
            }
        }
    }
}

关键修复点

  1. 手动焦点跟踪:为每个TextField创建FocusRequester,通过onFocusChanged记录当前聚焦的输入框,避免默认焦点自动跳转。
  2. 调整菜单锚点类型:使用MenuAnchorType.SecondaryEditable替代默认的PrimaryEditable,取消强制绑定单个锚点的焦点行为。
  3. 焦点恢复逻辑:在下拉菜单展开、选项选择完成后,将焦点恢复到之前选中的TextField。
  4. 禁用IME干扰:设置imeAction = ImeAction.None,避免键盘操作触发不必要的焦点切换。
  5. 过滤逻辑优化:明确derivedStateOf的依赖项,确保输入变化时过滤结果实时更新。

内容的提问来源于stack exchange,提问作者Surendra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 14:14:54