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

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

关键修改点说明

  1. 缓存输入状态:用derivedStateOf缓存valueProvider()的结果,避免在Composable的多个位置重复调用,减少不必要的状态计算和重组触发。
  2. 静态配置缓存:将colors、trailingIconText这些不随输入值变化的对象用remember包裹,避免每次重组都重新创建实例,降低性能开销。
  3. 修复焦点与键盘冲突:清空文本后延迟请求焦点(用LaunchedEffect+delay),避免和IME的关闭逻辑直接冲突,解决键盘无法再次唤起的问题。
  4. 缓存imeAction状态:根据当前输入值缓存imeAction,避免每次重组都实时判断,减少计算量。
  5. 统一状态判断:所有依赖输入值的判断(如isError、trailingIcon显示)都使用缓存后的currentValue.value,确保状态一致性,减少重组触发点。

额外优化建议

  • 检查ViewModel中onValueChange的处理逻辑,确保没有同步执行耗时操作(比如数据库写入、网络请求),如果有需要放到协程中异步执行。
  • 使用Compose Profiler工具查看重组次数,确认优化后的效果,定位是否还有其他过度重组的节点。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 11:54:57