Jetpack Compose如何设置Text省略号显示在第9行中间位置
问题根源
Compose的Text组件maxLines参数仅支持整型传入,设置8.5这类小数值不会生效;同时自带的TextOverflow.Ellipsis逻辑固定将省略号渲染在指定最大行数的行尾,没有提供调整省略号位置的配置项,因此没法直接实现第9行中间截断、后缀跟操作文本的效果。
效果参考
- 目标实现效果

- 默认实现效果

实现方案
放弃组件自带的省略号逻辑,手动计算文本截断位置,拼接正文、省略号、可点击的操作文本即可,核心逻辑:
- 通过
onTextLayout回调拿到文本的布局信息,判断文本是否超出指定行数 - 提前测量省略号+操作文本的总宽度,计算第9行剩余可用于渲染正文的宽度
- 根据可用宽度算出第9行能容纳的最后一个正文偏移量,截断正文后拼接后缀
- 用
AnnotatedString给操作文本加单独样式和点击标记,实现点击展开/收起逻辑
可直接复用的实现代码:
@Composable fun ExpandableText( text: String, seeMoreText: String = "See more", seeLessText: String = "See less", collapsedMaxLine: Int = 9, ) { var expanded by rememberSaveable { mutableStateOf(false) } val bodyStyle = MaterialTheme.typography.body1.copy( lineHeight = 20.sp, color = MaterialTheme.ts.colors.onBackgroundSecondary ) val actionStyle = MaterialTheme.typography.body2.copy( color = MaterialTheme.ts.colors.actionColor ) val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) } val suffixText = "... $seeMoreText" // 测量后缀文本总宽度,Compose 1.3+可用rememberTextMeasurer val textMeasurer = rememberTextMeasurer() val suffixWidth = remember(actionStyle, suffixText) { textMeasurer.measure( text = AnnotatedString(suffixText), style = actionStyle ).size.width } val displayText = remember(text, layoutResult.value, expanded, suffixWidth) { // 展开状态、布局未完成、文本行数不足时直接返回原文本 if (expanded || layoutResult.value == null || layoutResult.value!!.lineCount <= collapsedMaxLine) { AnnotatedString(text) } else { val textLayout = layoutResult.value!! val lastLineIndex = collapsedMaxLine - 1 // 计算第9行扣除后缀宽度后的可用正文宽度 val lastLineRight = textLayout.getLineRight(lastLineIndex) val availableWidth = lastLineRight - suffixWidth // 拿到可用宽度对应的最后一个正文字符偏移 val lastCharOffset = textLayout.getOffsetForPosition( Offset( x = availableWidth, y = textLayout.getLineTop(lastLineIndex) + textLayout.getLineHeight(lastLineIndex) / 2 ) ) // 拼接文本,给操作按钮加样式和点击标记 buildAnnotatedString { append(text.substring(0, lastCharOffset)) append("... ") pushStringAnnotation(tag = "expand_toggle", annotation = "expand") withStyle(actionStyle.toSpanStyle()) { append(seeMoreText) } pop() } } } ClickableText( modifier = Modifier.padding(top = 16.dp), text = displayText, style = bodyStyle, maxLines = if (expanded) Int.MAX_VALUE else collapsedMaxLine, onTextLayout = { layoutResult.value = it }, onClick = { clickOffset -> displayText.getStringAnnotations( tag = "expand_toggle", start = clickOffset, end = clickOffset ).firstOrNull()?.let { expanded = true } } ) // 展开状态下显示收起按钮 if (expanded) { Text( modifier = Modifier .padding(top = 16.dp, bottom = 40.dp) .clickable { expanded = false }, text = seeLessText, style = actionStyle ) } else { Spacer(modifier = Modifier.height(40.dp)) } }
适配说明
- 如果项目使用的Compose版本低于1.3,没有
rememberTextMeasurer,可以添加一个隐藏的Text组件渲染后缀文本,通过onSizeChanged回调拿到宽度值即可 - 如果正文包含富文本、内联图片、手动换行符,需要在计算截断偏移时额外判断,避免截断特殊字符导致显示异常
- 调整
collapsedMaxLine参数即可复用实现任意行数的中间截断效果,不需要修改核心逻辑
内容的提问来源于stack exchange,提问作者Android Dev
相关产品推荐
相关产品推荐

