如何在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) }
代码说明
- 正则匹配:
Regex("(#\\S+)|(@\\S+)")确保能准确抓取所有以#或@开头的连续非空白字符内容,避免误匹配空格后的字符。 - 文本拆分:通过
pattern.split(text)拆分文本,结合pattern.findAll提取所有标签/提及,保证后续遍历能精准对应片段类型。 - 动态构建注解文本:遍历拆分后的片段,对标签/提及添加专属点击注解和高亮样式,普通文本保留默认样式。
- 点击事件处理:根据点击位置获取对应注解,分别触发话题标签和提及的业务逻辑,比如页面跳转、弹窗提示等。
内容的提问来源于stack exchange,提问作者gaohomway
相关产品推荐
相关产品推荐

