Jetpack Compose自定义焦点指示在交互后消失的问题求解
解决Jetpack Compose自定义焦点指示丢失问题
问题根源
- 焦点状态跟踪逻辑不可靠:自定义
MyHighlightIndicationNode用本地变量isFocused和手动计数focusCount跟踪焦点,无法应对Compose重组或toggleable/clickable这类复杂交互流,容易出现状态丢失。 - 系统默认交互清除焦点:点击可交互组件时,系统会自动清除键盘焦点,导致焦点边框消失。
- 重组时状态未持久化:本地变量
isFocused不属于Compose快照状态,组件重组后会被重置为初始值。
解决方案
1. 重构自定义焦点指示节点,使用官方状态API跟踪焦点
替换手动计数逻辑,改用官方提供的collectIsFocusedAsState(),该API基于Compose快照状态,能在重组时稳定保持焦点状态:
private class MyHighlightIndicationNode( private val interactionSource: InteractionSource, private val themedIndicationColor: Color ) : Modifier.Node(), DrawModifierNode { // 用快照状态保存焦点状态,重组时不会丢失 private val isFocused by interactionSource.collectIsFocusedAsState() override fun ContentDrawScope.draw() { drawContent() if (isFocused) { drawRoundRect( color = themedIndicationColor, size = size, cornerRadius = CornerRadius(12.dp.toPx(), 12.dp.toPx()), style = Stroke(width = 2.dp.toPx()), alpha = 1.0f ) } } } class MyHighlightIndication(private val themedIndicationColor: Color) : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return MyHighlightIndicationNode(interactionSource, themedIndicationColor) } override fun hashCode(): Int = themedIndicationColor.hashCode() override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as MyHighlightIndication return themedIndicationColor == other.themedIndicationColor } }
2. 配置可交互组件,操作后强制保持焦点
使用FocusRequester管理焦点,在toggleable/clickable操作完成后手动请求焦点,覆盖系统默认的清除焦点行为:
@Composable private fun GoodExample7( snackbarLauncher: SnackbarLauncher? ) { val cardClickMessage = stringResource(id = R.string.custom_focus_indicators_example_7_message) val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } var isCheck by remember { mutableStateOf(false) } // 创建焦点请求器,用于手动控制焦点 val focusRequester = remember { FocusRequester() } OutlinedCard( onClick = { snackbarLauncher?.show(cardClickMessage) // 点击后重新请求焦点,避免焦点丢失 focusRequester.requestFocus() }, modifier = Modifier .padding(top = 8.dp) .focusRequester(focusRequester) .focusable(true) // 确保组件具备获取焦点的能力 .indication( interactionSource = interactionSource, indication = MyHighlightIndication( themedIndicationColor = MaterialTheme.colorScheme.primary ) ) .toggleable( value = isCheck, onValueChange = { isCheck = it // 切换状态后重新请求焦点 focusRequester.requestFocus() }, role = Role.Checkbox, interactionSource = interactionSource, indication = ripple() ), interactionSource = interactionSource ) { Row ( horizontalArrangement = Arrangement.Center, modifier = Modifier.padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically ) { BodyText( textId = R.string.custom_focus_indicators_example_7_card_description, modifier = Modifier ) Checkbox(checked = isCheck, onCheckedChange = null) } } }
3. 额外优化点
- 确保所有关联的可交互组件共享同一个
interactionSource,保持交互状态的一致性。 - 全局界面更新(如进度条刷新)时,由于焦点状态基于快照状态管理,会自动同步,不会出现丢失情况。
关键说明
collectIsFocusedAsState()是官方提供的可靠API,无需手动处理FocusInteraction流,能精准跟踪组件焦点状态。FocusRequester强制在交互操作后重新获取焦点,解决了系统默认的"点击清除焦点"问题。- 使用Compose快照状态存储焦点状态,彻底避免重组导致的状态丢失。
内容的提问来源于stack exchange,提问作者Amit Kundu
相关产品推荐
相关产品推荐

