so I am using a Text()
composable like so:
Text(
text = "this is some sample text that is long and so it is
ellipsized",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
and it ellipsizes the text properly:
The issue is that I want a See More
tag at the end of the ellipsis, prompting the user to expand the visible text box. How would I go about adding that?
To solve this you need to use onTextLayout
to get TextLayoutResult
: it contains all info about the state of drawn text.
Making it work for multiple lines is a tricky task. To do that you need to calculate sizes of both ellipsized text and "... See more" text, then, when you have both values you need to calculate how much text needs to be removed so "... See more" fits perfectly at the end of line.
The last item is SubcomposeLayout
, it would allow you making the calculations in one layout cycle, so your UI won't jump.
@Composable
fun ExpandableText(
text: String,
modifier: Modifier = Modifier,
minimizedMaxLines: Int = 1,
) {
var expanded by rememberSaveable { mutableStateOf(false) }
SubcomposeLayout(modifier) { constraints ->
var textLayoutResultVar: TextLayoutResult? = null
var seeMoreSizeVar: IntSize? = null
var textComposable = subcompose("full text") {
Text(
text = text,
onTextLayout = { textLayoutResultVar = it },
)
}.first().measure(constraints)
val seeMoreText = subcompose("see more") {
Text(
"... See more",
onTextLayout = { seeMoreSizeVar = it.size },
modifier = Modifier
.clickable {
expanded = true
}
)
}.first().measure(Constraints())
// allowing smart cast
val textLayoutResult = textLayoutResultVar
val seeMoreSize = seeMoreSizeVar
val textWidth = textComposable.width
val lastLineIndex = minimizedMaxLines - 1
val seeMoreOffset: Offset?
if (!expanded && textLayoutResult != null && seeMoreSize != null
&& textLayoutResult.lineCount > lastLineIndex
) {
var lastCharIndex = textLayoutResult.getLineEnd(lastLineIndex, visibleEnd = true) + 1
var charRect: Rect
do {
lastCharIndex -= 1
charRect = textLayoutResult.getCursorRect(lastCharIndex)
} while (
charRect.left > textLayoutResult.size.width - seeMoreSize.width
)
seeMoreOffset = Offset(charRect.left, charRect.bottom - seeMoreSize.height)
textComposable = subcompose("cutText") {
Text(
text = text.substring(startIndex = 0, endIndex = lastCharIndex),
)
}.first().measure(constraints)
} else {
seeMoreOffset = null
}
layout(textWidth, textComposable.height) {
textComposable.place(0, 0)
if (seeMoreOffset != null) {
seeMoreText.place(
x = seeMoreOffset.x.toInt(),
y = seeMoreOffset.y.toInt()
)
}
}
}
}