前言
文本展开和收起功能可以在需要显示较长文本或内容丰富的情况下,提供更好的用户体验和页面可读性,同时减少页面的冗余信息。可以说是发中常见的功能之一。
需要实现的点
- 文本超出最大显示行数时尾部显示"...展开"
- 点击"...展开"时显示全部文本并且尾部显示"...收起"
看看Text为我们提供了啥
查看Text的源码很容易看到overflow和maxLines两个属性。
Kotlin
@Composable
fun Text(
// 要显示的文本内容
text: String,
// Modifier修饰符
modifier: Modifier = Modifier,
// 文本内容溢出处理方式:Clip、Ellipsis、Visible
overflow: TextOverflow = TextOverflow.Clip,
// 最大文本行数
maxLines: Int = Int.MAX_VALUE,
// 计算新文本布局时执行的回调。
onTextLayout: (TextLayoutResult) -> Unit = {},
// 文本的样式配置
style: TextStyle = LocalTextStyle.current
)
当我们的overflow设置为"Ellipsis"且文本超出最大行数时显示"..."。
因此我们的思路很简单,当文本控件显示"..."时在其尾部盖上"...展开",伪代码如下:
Kotlin
Box {
Text()
Text(
text = "...展开",
modifier = Modifier
.graphicsLayer {
translationX = ellipsisLeft
translationY = ellipsisBottom - size.height
}
)
}
如何获得文本内容溢出后的位置
还好Text控件提供了onTextLayout回调返回TextLayoutResult,这里贴对我们有用的几个方法:
Kotlin
class TextLayoutResult constructor(
val layoutInput: TextLayoutInput,
val multiParagraph: MultiParagraph,
val size: IntSize
) {
val lineCount: Int get() = multiParagraph.lineCount
fun isLineEllipsized(lineIndex: Int): Boolean = multiParagraph.isLineEllipsized(lineIndex)
fun getLineTop(lineIndex: Int): Float = multiParagraph.getLineTop(lineIndex)
fun getLineBottom(lineIndex: Int): Float = multiParagraph.getLineBottom(lineIndex)
/**
* 获取指定行右侧X坐标
*/
fun getLineRight(lineIndex: Int): Float = multiParagraph.getLineRight(lineIndex)
/**
* 获取指定位置字符的水平偏移量
*/
fun getHorizontalPosition(offset: Int, usePrimaryDirection: Boolean): Float =
multiParagraph.getHorizontalPosition(offset, usePrimaryDirection)
/**
* 获取指定偏移量最接近的字符位置。
*/
fun getOffsetForPosition(position: Offset): Int =
multiParagraph.getOffsetForPosition(position)
}
ellipsisBottom可以通过getLineBottom直接获取取。
ellipsisLeft就相对要麻烦些,我们先要获取指定行右侧X坐标再减去ellipsisText的width。
先计算下ellipsisText的width,好在compose为我们提供非常便捷的api这里直接贴代码:
Kotlin
val ellipsisMeasure = rememberTextMeasurer()
val ellipsisLayoutResult = ellipsisMeasure.measure(
text = ellipsisText,
style = style
)
val ellipsisWidth = ellipsisLayoutResult.size.width
但是直接使用会出现文字部分被覆盖的情况,所以我们通过该值先获取指定偏移量最接近的字符位置,再通过获取指定位置字符的水平偏移量得到真正的值。
Kotlin
val ellipsisLeft = it.getHorizontalPosition(
it.getOffsetForPosition(
Offset(
it.getLineRight(it.lineCount - 1) - ellipsisWidth,
it.getLineTop(it.lineCount - 1)
)
), true
)
完整代码
Kotlin
@Composable
fun EllipsisText(
text: AnnotatedString,
color: Color,
backgroundColor: Color,
fontSize: TextUnit = TextUnit.Unspecified,
lineHeight: TextUnit = TextUnit.Unspecified,
maxLines: Int = Int.MAX_VALUE,
inlineContent: Map<String, InlineTextContent> = mapOf(),
onTextLayout: (TextLayoutResult) -> Unit = {},
ellipsisText: String = "...全文",
ellipsisColor: Color = colorResource(R.color.blue),
onClick: () -> Unit = {},
onEllipsisClick: () -> Unit = {},
) {
val style = TextStyle.Default.copy(color = color, fontSize = fontSize)
var ellipsisBottom by remember { mutableFloatStateOf(0f) }
var ellipsisLeft by remember { mutableFloatStateOf(0f) }
val ellipsisMeasure = rememberTextMeasurer()
val ellipsisLayoutResult = ellipsisMeasure.measure(
text = ellipsisText,
style = style
)
val ellipsisWidth = ellipsisLayoutResult.size.width
Box(modifier = Modifier.animateContentSize()) {
Text(
text = text,
modifier = Modifier
.clickable(
onClick = onClick,
indication = null,
interactionSource = remember { MutableInteractionSource() }
)
.background(backgroundColor),
lineHeight = lineHeight,
overflow = TextOverflow.Ellipsis,
maxLines = maxLines,
inlineContent = inlineContent,
onTextLayout = {
val offset = if (maxLines == Int.MAX_VALUE) 0 else ellipsisWidth
ellipsisBottom = it.getLineBottom(it.lineCount - 1)
ellipsisLeft = it.getHorizontalPosition(
it.getOffsetForPosition(
Offset(
it.getLineRight(it.lineCount - 1) - offset,
it.getLineTop(it.lineCount - 1)
)
), true
)
if (ellipsisLeft + ellipsisWidth > it.size.width) {
ellipsisLeft = it.getHorizontalPosition(
it.getOffsetForPosition(
Offset(
(it.size.width - ellipsisWidth).toFloat(),
it.getLineTop(it.lineCount - 1)
)
), true
)
}
onTextLayout(it)
},
style = style
)
Text(
text = "$ellipsisText ",
modifier = Modifier
.graphicsLayer {
translationX = ellipsisLeft
translationY = ellipsisBottom - size.height
}
.clickable { onEllipsisClick() }
.background(backgroundColor),
style = style.copy(color = ellipsisColor)
)
}
}
如何使用
Kotlin
var ellipsis by remember { mutableStateOf(false) }
var expand by remember { mutableStateOf(false) }
EllipsisText(
text = buildAnnotatedString {
append(
"I am happy to join with you today in what will go down in history as the greatest demonstration for freedom in the history of our nation."
)
},
color = colorResource(R.color.text_333),
backgroundColor = colorResource(R.color.background),
fontSize = 14.sp,
maxLines = if (expand) Int.MAX_VALUE else 2,
onTextLayout = {
ellipsis = it.isLineEllipsized(it.lineCount - 1)
},
ellipsisText = if (expand) {
"...收起"
} else if (ellipsis) {
"...展开"
} else {
""
}
) {
expand = !expand
}
Thanks
以上就是本篇文章的全部内容,如有问题欢迎指出,我们一起进步。
如果觉得本篇文章对您有帮助的话请点个赞让更多人看到吧,您的鼓励是我前进的动力。
谢谢~~