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

Jetpack Compose中可提及TextField样式丢失问题求助

解决Jetpack Compose TextField提及样式丢失问题

问题原因

样式丢失的核心原因是**onValueChange回调返回的TextFieldValue是系统默认生成的,不会保留你手动添加的SpanStyle**。当你继续输入时,系统会用无样式的AnnotatedString覆盖之前带样式的内容,导致提及格式失效。

解决方案

需要将文本内容与样式渲染逻辑分离,每次文本变化(包括用户输入和选中成员插入)时,都重新生成带样式的AnnotatedString,而非依赖系统自动生成的TextFieldValue。

步骤1:重构状态存储

不再直接存储TextFieldValue,将文本内容和光标位置分开存储,确保每次文本变化都能重新生成样式:

// 单独存储文本内容
var inputText by remember { mutableStateOf("") }
// 单独存储光标位置
var inputSelection by remember { mutableStateOf(TextRange.Zero) }

// 每次文本变化时,重新生成带提及样式的AnnotatedString
val annotatedInputText = remember(inputText) {
    buildAnnotatedMentionString(inputText)
}

// 构建用于TextField的TextFieldValue
val textFieldValue = TextFieldValue(
    annotatedString = annotatedInputText,
    selection = inputSelection
)

步骤2:提取样式生成函数

把生成提及样式的逻辑封装成独立函数,方便重复调用:

private fun buildAnnotatedMentionString(text: String): AnnotatedString {
    // 匹配@{成员名} 格式的正则(根据需求调整,这里匹配严格的@{xxx}格式)
    val mentionRegex = Regex("@\\{[^}]+\\}\\s")
    return buildAnnotatedString {
        var lastPosition = 0
        mentionRegex.findAll(text).forEach { matchResult ->
            val matchRange = matchResult.range
            // 追加非提及部分的文本
            append(text.substring(lastPosition, matchRange.first))
            // 为提及部分应用绿色粗体样式
            withStyle(
                style = SpanStyle(
                    fontWeight = FontWeight.Bold,
                    color = Color.Green
                )
            ) {
                append(text.substring(matchRange))
            }
            lastPosition = matchRange.last + 1
        }
        // 追加剩余的非提及文本
        if (lastPosition < text.length) {
            append(text.substring(lastPosition))
        }
    }
}

步骤3:修改TextField的onValueChange逻辑

在onValueChange中更新文本和光标状态,确保每次输入都重新应用样式:

TextField(
    value = textFieldValue,
    onValueChange = { newTextFieldValue ->
        // 更新文本内容和光标位置
        inputText = newTextFieldValue.text
        inputSelection = newTextFieldValue.selection
        
        // 回调时传递带样式的TextFieldValue(根据业务需求调整)
        val styledTextFieldValue = newTextFieldValue.copy(annotatedString = annotatedInputText)
        callBack(ChatInputAction.OnTextChange(styledTextFieldValue))
    },
    // 其他原有属性保持不变
    textStyle = TextStyle(fontSize = 18.sp, color = Gray1),
    placeholder = {
        Text(
            text = stringResource(id = R.string.reply),
            fontSize = 18.sp,
            color = Gray1
        )
    },
    colors = TextFieldDefaults.textFieldColors(
        containerColor = Color.Transparent,
        unfocusedIndicatorColor = Color.Transparent,
        focusedIndicatorColor = Color.Transparent
    ),
    maxLines = 5,
    modifier = Modifier
        .fillMaxWidth()
        .weight(1f)
        .focusRequester(focusRequester)
        .onFocusChanged {
            callBack(ChatInputAction.OnFocusChange)
        }
)

步骤4:修正成员列表点击逻辑

确保插入的文本符合@{memberName}格式,并更新对应的状态:

// 成员列表点击事件
val currentCursorPos = inputSelection.start
// 插入@{成员名} 格式的文本
val newText = inputText.replaceRange(
    startMentionKeyWordIndex,
    currentCursorPos,
    "@{${it.name}} "
)
// 更新文本和光标位置
inputText = newText
// 光标移动到插入内容的末尾(@{name} 长度为name.length+3,加上空格共+4)
inputSelection = TextRange(currentCursorPos + it.name.length + 4)

关键注意点

  1. 正则匹配调整:如果你的提及格式是@成员名而非@{成员名},可将正则改为Regex("@\\S+\\s")。
  2. 状态同步:光标位置必须和文本内容同步更新,避免出现光标错位问题。
  3. 转义修正:Kotlin字符串中单个\即可完成转义,原代码中的@\\\\S+\\\\s是错误写法。

内容的提问来源于stack exchange,提问作者Ta Van Co

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 22:35:14