这一节主要了解一下Compose中的ClickableText,在Jetpack Compose中,ClickableText是用于创建可点击文本的组件,其核心功能是通过声明式语法将文本设置为交互式元素,用户点击时可触发特定操作。简单总结如下:
API含义
text:要显示的文本内容(AnnotatedString类型,支持富文本样式)。
onClick:点击事件的回调函数,接收点击位置的偏移量作为参数,用于定位具体点击的字符。
style:文本样式(如颜色、字体大小),可通过TextStyle自定义。
modifier:可选修饰符,用于添加额外交互或布局属性。
富文本支持:通过AnnotatedString可为文本添加元数据(如链接、样式标签),结合ClickableText实现复杂交互。
场景
1 基础交互
创建可点击的按钮或链接(如“登录”“注册”)。
实现文本展开/折叠功能(点击“显示更多”展开隐藏内容)。
2 富文本交互
在文章中嵌入可点击的关键词或参考文献链接。
开发聊天应用时,将用户名或话题标签设置为可点击(如@用户或#话题)。
3 导航与跳转
结合路由库(如Compose Navigation)实现页面跳转。
栗子:
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.ClickableText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp@Composable
fun TestLinkTextExample(modifier: Modifier = Modifier) {val uriHandler = LocalUriHandler.currentval annotatedText = buildAnnotatedString {append("请阅读我们的 ")val termsStart = lengthappend("用户协议")val termsEnd = lengthaddStringAnnotation(tag = "URL", annotation = "https://example.com/terms", // 换成具体业务链接start = termsStart,end = termsEnd)addStyle(style = SpanStyle(color = Color(0xFF2196F3),textDecoration = TextDecoration.Underline),start = termsStart,end = termsEnd)append(" 和 ")val privacyStart = lengthappend("隐私政策")val privacyEnd = lengthaddStringAnnotation(tag = "URL",annotation = "https://examplexxx.com/privacy",//换成具体业务链接start = privacyStart,end = privacyEnd)addStyle(style = SpanStyle(color = Color(0xFF2196F3),textDecoration = TextDecoration.Underline),start = privacyStart,end = privacyEnd)}ClickableText(text = annotatedText,style = TextStyle(fontSize = 16.sp,color = Color.Black),modifier = modifier.padding(16.dp),onClick = { offset ->annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset).firstOrNull()?.let { annotation ->uriHandler.openUri(annotation.item)}})
}
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp@Composable
fun TestTextExample(modifier: Modifier = Modifier) {var showDialog by remember { mutableStateOf(false) }var dialogMessage by remember { mutableStateOf("") }// 构建带@提及和#话题的文本val annotatedText = buildAnnotatedString {val userStart = lengthappend("@JetpackCompose")val userEnd = lengthaddStringAnnotation(tag = "MENTION",annotation = "JetpackCompose", start = userStart,end = userEnd)addStyle(style = SpanStyle(color = Color(0xFF9C27B0)), start = userStart,end = userEnd)append(" 发布了关于 ")val topicStart = lengthappend("#Compose开发")val topicEnd = lengthaddStringAnnotation(tag = "TOPIC", annotation = "Compose开发", start = topicStart,end = topicEnd)addStyle(style = SpanStyle(color = Color(0xFFF44336)), start = topicStart,end = topicEnd)append(" 的新内容,快去看看吧!")}fun handleClick(offset: Int) {annotatedText.getStringAnnotations(tag = "MENTION", start = offset, end = offset).firstOrNull()?.let {dialogMessage = "查看用户: ${it.item}"showDialog = truereturn}annotatedText.getStringAnnotations(tag = "TOPIC", start = offset, end = offset).firstOrNull()?.let {dialogMessage = "查看话题: ${it.item}"showDialog = truereturn}}if (showDialog) {AlertDialog(onDismissRequest = { showDialog = false },title = { Text("操作提示") },text = { Text(dialogMessage) },confirmButton = {Text(text = "确定",modifier = Modifier.clickable { showDialog = false })})}ClickableText(text = annotatedText,style = TextStyle(fontSize = 16.sp,color = Color.Black),modifier = modifier.padding(16.dp),onClick = ::handleClick)
}
注意
1 点击区域定位:onClick回调返回的是字符偏移量,需通过AnnotatedString的元数据定位具体点击的文本片段。若文本包含多语言或复杂排版,需额外处理偏移量计算。
2 性能优化:避免在onClick中执行耗时操作,否则可能导致界面卡顿。
3 无障碍支持:为可点击文本添加semantic描述,提升无障碍体验。
简单看一下其源码:
@Composable
@Deprecated("Use Text or BasicText and pass an AnnotatedString that contains a LinkAnnotation")
fun ClickableText(text: AnnotatedString,modifier: Modifier = Modifier,style: TextStyle = TextStyle.Default,softWrap: Boolean = true,overflow: TextOverflow = TextOverflow.Clip,maxLines: Int = Int.MAX_VALUE,onTextLayout: (TextLayoutResult) -> Unit = {},onClick: (Int) -> Unit
) {// 1val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }// 2 val pressIndicator = Modifier.pointerInput(onClick) {detectTapGestures { pos ->layoutResult.value?.let { layoutResult ->onClick(layoutResult.getOffsetForPosition(pos))}}}// 3BasicText(text = text,modifier = modifier.then(pressIndicator),style = style,softWrap = softWrap,overflow = overflow,maxLines = maxLines,onTextLayout = {layoutResult.value = itonTextLayout(it)})
}
分析:
1.状态定义:缓存文本布局结果
layoutResult用于mutableStateOf存储TextLayoutResult对象,用于记录文本的布局信息(如字符位置、行高、宽高等)。
remember 确保重组时不会重复初始化,仅在依赖变化时更新。
2.点击事件处理:pointerInput检测点击并计算索引
pointerInput:Compose中处理低级触摸事件的入口,传入onClick作为键(当onClick变化时,重新创建触摸事件处理器)。
detectTapGestures:手势检测器,监听点击事件,pos参数是点击位置相对于组件左上角的坐标(Offset类型,包含x和y偏移)。
索引计算:当点击发生时,通过layoutResult.getOffsetForPosition(pos)将点击坐标转换为字符索引(核心逻辑与新版一致),再通过onClick回调暴露该索引。
3.文本渲染:BasicText与布局结果同步
BasicText:Compose中最基础的文本渲染组件,负责将AnnotatedString渲染到屏幕上。
修饰符组合:通过modifier.then(pressIndicator)将点击事件修饰符(pressIndicator)与外部传入的modifier组合,确保点击事件能被正确监听。
布局结果同步:onTextLayout回调在文本布局完成后触发,将最新的TextLayoutResult存入layoutResult缓存,为点击索引计算提供数据支持