Jetpack Compose TextField首次按键卡顿及键盘无法二次打开问题
Jetpack Compose TextField首次按键卡顿+键盘无法重复唤起问题修复
核心问题定位
你的代码里有几个关键问题导致了首次按键卡顿和键盘唤起异常:
- 频繁调用
valueProvider(),未缓存状态导致重复计算 - 静态配置(如颜色、图标Composable)未用
remember缓存,每次重组都重新创建 - 焦点请求逻辑和IME行为冲突
- DecorationBox内的状态判断重复触发重组
修复后的完整代码
@OptIn(ExperimentalMaterial3Api::class) @Composable fun AppInputText( modifier: Modifier = Modifier, valueProvider: () -> String, onValueChange: (String) -> Unit, labelText: String = "ilość", labelTextStyle: TextStyle = MaterialTheme.typography.labelMedium.copy( fontFamily = FontFamily(Font(R.font.open_sans_extrabold)), ), isSuffixEnabled: Boolean = false, inputTextStyle: TextStyle = MaterialTheme.typography.titleLarge.copy( lineHeight = 22.sp, fontWeight = FontWeight.Bold, color = Color(0xFF495A81).copy(alpha = 0.78f), textAlign = if (isSuffixEnabled) TextAlign.End else TextAlign.Start, ), registerQuantity: () -> Unit = {}, isEnabled: Boolean = true, isReadOnly: Boolean = false, keyboardType: KeyboardType = KeyboardType.Number, amount: Int = 1, placeholderText: String = "Zeskanuj albo wpisz…", ) { // 缓存当前输入值,避免重复调用valueProvider() val currentValue = remember { derivedStateOf(valueProvider) } val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } val singleLine = true // 用remember缓存静态颜色配置,避免每次重组重建 val colors = remember { OutlinedTextFieldDefaults.colors( focusedBorderColor = Color(0xFF667396), unfocusedBorderColor = Color(0xFF8892AC), focusedLabelColor = Color(0xFF8892AC), unfocusedLabelColor = Color(0xFF8892AC), focusedContainerColor = Color.White, unfocusedContainerColor = Color.White, errorContainerColor = Color.White, disabledContainerColor = Color.White, focusedTextColor = Color(0xFF495A81).copy(alpha = 0.78f), unfocusedTextColor = Color(0xFF495A81).copy(alpha = 0.78f), unfocusedTrailingIconColor = Color(0xFF495A81).copy(alpha = 0.6f), focusedTrailingIconColor = Color(0xFF495A81).copy(alpha = 0.6f), ) } val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current // 用remember缓存trailingIcon,避免每次重组重建 val trailingIconText = remember { @Composable { Icon( modifier = Modifier .clip(CircleShape) .clickable { onValueChange("") // 延迟请求焦点,避免和IME关闭逻辑冲突 LaunchedEffect(Unit) { delay(100) focusRequester.requestFocus() } } .requiredSize(34.dp) .padding(5.dp) .background( color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f), shape = CircleShape ) .padding(4.dp), imageVector = Icons.Filled.Close, tint = MaterialTheme.colorScheme.surface.copy(alpha = 0.87f), contentDescription = "clear icon" ) } } // 缓存imeAction状态,避免实时计算 val imeAction = remember(currentValue.value) { if (currentValue.value.isNotBlank()) ImeAction.Done else ImeAction.Send } BasicTextField( value = currentValue.value, onValueChange = onValueChange, modifier = modifier .defaultMinSize(minHeight = 52.dp) .focusRequester(focusRequester), interactionSource = interactionSource, enabled = isEnabled, readOnly = isReadOnly, singleLine = singleLine, textStyle = inputTextStyle, keyboardOptions = KeyboardOptions( imeAction = imeAction, keyboardType = keyboardType, ), keyboardActions = KeyboardActions( onDone = { defaultKeyboardAction(ImeAction.Done) focusManager.clearFocus() registerQuantity() } ) ) { basicTextField -> OutlinedTextFieldDefaults.DecorationBox( isError = currentValue.value.isBlank(), placeholder = { if (currentValue.value.isBlank()) { Text( modifier = Modifier.padding(vertical = 6.dp), text = placeholderText, style = MaterialTheme.typography.labelMedium.copy( fontSize = 16.sp, fontFamily = FontFamily(Font(R.font.lato_light)), color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.46f), ) ) } }, value = currentValue.value, visualTransformation = VisualTransformation.None, innerTextField = basicTextField, label = { Text( text = labelText.uppercase(), style = labelTextStyle ) }, singleLine = singleLine, enabled = isEnabled, suffix = { if (isSuffixEnabled) { Row { Text( text = " z $amount", textAlign = TextAlign.Start, style = MaterialTheme.typography.titleLarge.copy( lineHeight = 22.sp, fontWeight = FontWeight.Bold, color = Color(0xFF495A81).copy(alpha = 0.78f), ), ) Spacer(modifier = Modifier.fillMaxWidth(0.8f)) } } }, trailingIcon = if (currentValue.value.isNotBlank() && !isReadOnly) trailingIconText else null, interactionSource = interactionSource, contentPadding = OutlinedTextFieldDefaults.contentPadding( top = 8.dp, bottom = 8.dp ), colors = colors, container = { OutlinedTextFieldDefaults.ContainerBox( enabled = isEnabled, isError = currentValue.value.isBlank(), colors = colors, interactionSource = interactionSource, shape = RoundedCornerShape(11.dp), unfocusedBorderThickness = 1.33.dp, focusedBorderThickness = 2.dp ) }, ) } }
关键修改点说明
- 缓存输入状态:用
derivedStateOf缓存valueProvider()的结果,避免在Composable的多个位置重复调用,减少不必要的状态计算和重组触发。 - 静态配置缓存:将
colors、trailingIconText这些不随输入值变化的对象用remember包裹,避免每次重组都重新创建实例,降低性能开销。 - 修复焦点与键盘冲突:清空文本后延迟请求焦点(用
LaunchedEffect+delay),避免和IME的关闭逻辑直接冲突,解决键盘无法再次唤起的问题。 - 缓存imeAction状态:根据当前输入值缓存
imeAction,避免每次重组都实时判断,减少计算量。 - 统一状态判断:所有依赖输入值的判断(如
isError、trailingIcon显示)都使用缓存后的currentValue.value,确保状态一致性,减少重组触发点。
额外优化建议
- 检查ViewModel中
onValueChange的处理逻辑,确保没有同步执行耗时操作(比如数据库写入、网络请求),如果有需要放到协程中异步执行。 - 使用Compose Profiler工具查看重组次数,确认优化后的效果,定位是否还有其他过度重组的节点。
内容的提问来源于stack exchange,提问作者Cuyer
相关产品推荐
相关产品推荐

