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

如何在Jetpack Compose中动态识别并实现话题标签与提及的超链接

技术问询:如何在Jetpack Compose中实现文本内话题标签(#)与提及(@)的超链接?

固定标签实现代码

@Composable
fun HashtagsAndMentions() {

    val colorScheme = MaterialTheme.colorScheme
    val primaryStyle = SpanStyle(color = colorScheme.primary)
    val textStyle = SpanStyle(color = colorScheme.onBackground)

    val annotatedString = buildAnnotatedString {

        withStyle(style = textStyle) {
            append("I am ")
        }

        pushStringAnnotation(tag = "hashtags", annotation = "hashtags")
        withStyle(style = primaryStyle) {
            append(text = "#hashtags")
        }
        pop()

        withStyle(style = textStyle) {
            append(" and ")
        }

        pushStringAnnotation(tag = "mentions", annotation = "mentions")
        withStyle(style = primaryStyle) {
            append(text = "@mentions")
        }
        pop()

        withStyle(style = textStyle) {
            append(" in Jetpack Compose.")
        }
    }

    ClickableText(
        onClick = {
            annotatedString.getStringAnnotations("hashtags", it, it).firstOrNull()?.let {
                // 处理话题标签点击逻辑
            }
            annotatedString.getStringAnnotations("mentions", it, it).firstOrNull()?.let {
                // 处理提及点击逻辑
            }
        },
        text = annotatedString,
        style = MaterialTheme.typography.bodyLarge,
        modifier = Modifier.padding(16.dp))
}
@Preview
@Composable
fun PreviewTest() {
    HashtagsAndMentions()
}

上述代码实现了固定标签的超链接功能,现在需要解决的核心问题是:如何动态识别文本中的话题标签(#)与提及(@)内容,并为其添加可点击的超链接?

解决方案:正则匹配+动态构建AnnotatedString

核心思路是通过正则表达式精准匹配文本中的话题标签(#+连续非空白字符)和提及(@+连续非空白字符),将原文本拆分为普通文本、话题标签、提及三类片段,再遍历片段构建带样式和点击注解的AnnotatedString。

完整实现代码

@Composable
fun DynamicHashtagsAndMentions(text: String) {
    val colorScheme = MaterialTheme.colorScheme
    val primaryStyle = SpanStyle(color = colorScheme.primary)
    val textStyle = SpanStyle(color = colorScheme.onBackground)

    // 正则规则:匹配#开头的话题标签、@开头的提及
    val pattern = Regex("(#\\S+)|(@\\S+)")
    // 将文本拆分为普通文本片段 + 标签/提及片段
    val textParts = pattern.split(text)
    // 提取所有匹配到的标签/提及内容
    val matchedTags = pattern.findAll(text).map { it.value }.toList()

    val annotatedString = buildAnnotatedString {
        var tagIndex = 0
        textParts.forEach { part ->
            if (part.isEmpty()) return@forEach
            
            val isHashtag = part.startsWith("#")
            val isMention = part.startsWith("@")
            
            if (isHashtag || isMention) {
                // 添加点击注解:存储去除前缀后的纯内容
                val annotationTag = if (isHashtag) "hashtag" else "mention"
                pushStringAnnotation(
                    tag = annotationTag,
                    annotation = part.removePrefix(if (isHashtag) "#" else "@")
                )
                // 设置高亮样式
                withStyle(primaryStyle) {
                    append(part)
                }
                pop()
                tagIndex++
            } else {
                // 普通文本使用默认样式
                withStyle(textStyle) {
                    append(part)
                }
            }
        }
    }

    ClickableText(
        onClick = { offset ->
            // 处理话题标签点击:跳转话题详情页等逻辑
            annotatedString.getStringAnnotations("hashtag", offset, offset).firstOrNull()?.let { annotation ->
                // 示例:Toast.makeText(context, "点击话题:${annotation.item}", Toast.LENGTH_SHORT).show()
            }
            // 处理提及点击:跳转用户主页等逻辑
            annotatedString.getStringAnnotations("mention", offset, offset).firstOrNull()?.let { annotation ->
                // 示例:Toast.makeText(context, "点击提及:${annotation.item}", Toast.LENGTH_SHORT).show()
            }
        },
        text = annotatedString,
        style = MaterialTheme.typography.bodyLarge,
        modifier = Modifier.padding(16.dp)
    )
}

@Preview
@Composable
fun PreviewDynamicHashtagsAndMentions() {
    val testText = "I am #JetpackCompose and @AndroidDev in Jetpack Compose. Let's talk about #ComposeUI!"
    DynamicHashtagsAndMentions(text = testText)
}

代码说明

  1. 正则匹配:Regex("(#\\S+)|(@\\S+)")确保能准确抓取所有以#或@开头的连续非空白字符内容,避免误匹配空格后的字符。
  2. 文本拆分:通过pattern.split(text)拆分文本,结合pattern.findAll提取所有标签/提及,保证后续遍历能精准对应片段类型。
  3. 动态构建注解文本:遍历拆分后的片段,对标签/提及添加专属点击注解和高亮样式,普通文本保留默认样式。
  4. 点击事件处理:根据点击位置获取对应注解,分别触发话题标签和提及的业务逻辑,比如页面跳转、弹窗提示等。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 16:05:38