I have a Text
object in Jetpack Compose whose content is continuously getting longer, but I only want to render the last 3 lines at any given time. Essentially, I'm hoping to mimic the behavior of TextView
's android:gravity="bottom"
, as seen here. How would I go about achieving this?
Asked
Active
Viewed 1,228 times
0

Quontas
- 400
- 1
- 3
- 19
-
The gravity attribute in the view system corresponds to the `align` Modifier in Compose. Try that. – Richard Onslow Roper Nov 04 '21 at 17:51
1 Answers
2
Text
has onTextLayout
parameter with will give you all needed information about the text layout. You can get the number of lines and the offset for the needed line index.
Using SubcomposeLayout
you can calculate the needed offset using value got from onTextLayout
before any drawing and apply it. And you need to apply Modifier.clipToBounds
to prevent redundant text to be drawn.
@Composable
fun LimitedText(
text: String,
maxBottomLines: Int,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
style: TextStyle = LocalTextStyle.current,
) {
SubcomposeLayout(
// prevent offset text from being drawn
modifier.clipToBounds()
) { constraints ->
var extraLinesHeight = 0
val placeable = subcompose(null) {
Text(
text = text,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
onTextLayout = { textLayoutResult ->
val extraLines = textLayoutResult.lineCount - maxBottomLines
if (extraLines > 0) {
extraLinesHeight = textLayoutResult.getLineTop(extraLines).roundToInt()
}
},
style = style,
)
}[0].measure(
// override maxWidth to get full text size
constraints.copy(maxHeight = Int.MAX_VALUE)
)
layout(
width = placeable.width,
height = placeable.height - extraLinesHeight
) {
placeable.place(0, -extraLinesHeight)
}
}
}
Usage:
LimitedText(
LoremIpsum().values.first(),
maxBottomLines = 3,
)
Most of time this would be enough, but in case you will need to get the final result of onTextLayout
, you can calculate the text offset and add one more placeable with subcompose
which will contain only the needed part of the text, and place only the second placeable.
@Composable
fun LimitedText(
text: String,
maxBottomLines: Int,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
) {
SubcomposeLayout(modifier) { constraints ->
var slotId = 0
fun placeText(
text: String,
onTextLayout: (TextLayoutResult) -> Unit,
constraints: Constraints,
) = subcompose(slotId++) {
Text(
text = text,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
onTextLayout = onTextLayout,
style = style,
)
}[0].measure(constraints)
var substringStartIndex: Int? = null
val initialPlaceable = placeText(
text = text,
// override maxWidth to get full text size
constraints = constraints.copy(maxHeight = Int.MAX_VALUE),
onTextLayout = { textLayoutResult ->
val extraLines = textLayoutResult.lineCount - maxBottomLines
if (extraLines > 0) {
substringStartIndex = textLayoutResult.run {
getOffsetForPosition(
Offset(1f, getLineTop(extraLines) + 1f)
)
}
} else {
onTextLayout(textLayoutResult)
}
},
)
val finalPlaceable = substringStartIndex?.let {
placeText(
text = text.substring(startIndex = it),
constraints = constraints,
onTextLayout = onTextLayout,
)
} ?: initialPlaceable
layout(
width = finalPlaceable.width,
height = finalPlaceable.height
) {
finalPlaceable.place(0, 0)
}
}
}

Phil Dukhov
- 67,741
- 15
- 184
- 220