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

如何在Jetpack Compose中实现动态Annotated String及自定义标注组件?

实现动态标注文本组件的方案

核心思路

要实现你想要的效果,核心是解析带占位符的文本,将占位符替换为对应内容并应用指定样式,最终生成AnnotatedString在Compose中显示。我们可以通过以下步骤完成:

  1. 定义数据类存储标注项的占位符、内容和样式
  2. 实现文本解析逻辑,替换占位符并拼接带样式的文本
  3. 封装成自定义Compose组件,简化调用

具体实现

1. 定义标注项数据类

先创建一个数据类,用来描述每个需要标注的文本项,包含占位符、实际内容和要应用的文本样式:

data class AnnotatedItem(
    val placeholder: String, // 比如 "$boldText"
    val content: String, // 实际要显示的文本
    val style: SpanStyle // 要应用的样式,比如加粗、红色
)

2. 实现文本解析工具函数

编写一个函数,负责将原始文本和标注项转换为AnnotatedString:

fun buildAnnotatedText(rawText: String, annotatedItems: List<AnnotatedItem>): AnnotatedString {
    val builder = AnnotatedString.Builder()
    var currentIndex = 0

    // 按占位符出现顺序排序,避免替换顺序混乱
    val sortedItems = annotatedItems.sortedBy { rawText.indexOf(it.placeholder) }
    
    sortedItems.forEach { item ->
        val placeholderStart = rawText.indexOf(item.placeholder, currentIndex)
        if (placeholderStart > currentIndex) {
            // 添加占位符之前的普通文本
            builder.append(rawText.substring(currentIndex, placeholderStart))
        }
        // 添加带样式的标注文本
        builder.append(
            AnnotatedString(
                text = item.content,
                spanStyle = item.style
            )
        )
        // 更新当前索引到占位符结束位置
        currentIndex = placeholderStart + item.placeholder.length
    }

    // 添加剩余的普通文本
    if (currentIndex < rawText.length) {
        builder.append(rawText.substring(currentIndex))
    }

    return builder.toAnnotatedString()
}

3. 封装自定义Compose组件

将上述逻辑封装成自定义组件,让调用更简洁:

@Composable
fun AnnotatedTextComponent(
    text: String,
    annotatedItems: List<AnnotatedItem>,
    modifier: Modifier = Modifier,
    style: TextStyle = LocalTextStyle.current
) {
    // 缓存生成的AnnotatedString,避免重组重复解析
    val annotatedString = remember(text, annotatedItems) {
        buildAnnotatedText(text, annotatedItems)
    }

    Text(
        text = annotatedString,
        modifier = modifier,
        style = style
    )
}

使用示例

按照你想要的方式调用组件,比如:

@Composable
fun DemoScreen() {
    val boldItem = AnnotatedItem(
        placeholder = "$boldText",
        content = "Bold content",
        style = SpanStyle(fontWeight = FontWeight.Bold)
    )

    val redItem = AnnotatedItem(
        placeholder = "$redText",
        content = "Red content",
        style = SpanStyle(color = Color.Red)
    )

    AnnotatedTextComponent(
        text = "$boldText in the beginning and $redText in the middle",
        annotatedItems = listOf(boldItem, redItem)
    )
}

扩展说明

  • 如果需要支持更多样式(比如下划线、斜体),只需要修改AnnotatedItem的style参数即可,Compose的SpanStyle支持所有文本样式属性
  • 如果占位符可能重复出现,上述工具函数会替换所有匹配的占位符;如果需要只替换第一个,可以调整indexOf的逻辑
  • 使用remember缓存生成的AnnotatedString,避免每次重组都重新解析,提升性能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 04:55:22