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