Search code examples
kotlinandroid-jetpack-composeandroid-jetpackandroid-jetpack-compose-text

Jetpack Compose - TextOverflow.Ellipsis doesn't work without specifying maxLines


I want to display a Text inside a Card with some inner padding and sometimes the text will not fit in. I want this thing to be marked with an ellipsis. But I can't make it work without maxLines.

@Composable
fun CardWithText() {
    Card(
        modifier = Modifier
            .height(60.dp)
            .width(100.dp)
            .border(1.dp, Color.Black, RoundedCornerShape(0))
    ) {
        Card(
            modifier = Modifier
                .padding(8.dp)
                .fillMaxSize()
                .border(1.dp, Color.Black, RoundedCornerShape(0))
        ) {
            Text(
                text = "One two three four five six seven eight nine ten eleven twelve",
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
                color = Color.Black
            )
        }
    }
}

With maxLines = 2

enter image description here

With maxLines = 3 or not using maxLines at all

enter image description here


Solution

  • This is a known issue, causing Ellipsis to ignore parent size constraints. Star it to bring more attention and follow the updates.

    Meanwhile you can use this hacky solution: it'll calculate the real number of lines and pass the correct value for maxLines:

    @Composable
    fun TextEllipsisFixed(
        text: String,
        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,
        softWrap: Boolean = true,
        maxLines: Int = Int.MAX_VALUE,
        onTextLayout: (TextLayoutResult) -> Unit,
        style: TextStyle = LocalTextStyle.current,
    ) {
        SubcomposeLayout(modifier = modifier) { constraints ->
            var slotId = 0
            fun placeText(
                text: String,
                onTextLayout: (TextLayoutResult) -> Unit,
                constraints: Constraints,
                maxLines: Int,
            ) = subcompose(slotId++) {
                Text(
                    text = text,
                    color = color,
                    fontSize = fontSize,
                    fontStyle = fontStyle,
                    fontWeight = fontWeight,
                    fontFamily = fontFamily,
                    letterSpacing = letterSpacing,
                    textDecoration = textDecoration,
                    textAlign = textAlign,
                    lineHeight = lineHeight,
                    softWrap = softWrap,
                    onTextLayout = onTextLayout,
                    style = style,
                    overflow = TextOverflow.Ellipsis,
                    maxLines = maxLines,
                )
            }[0].measure(constraints)
            var textLayoutResult: TextLayoutResult? = null
            val initialPlaceable = placeText(
                text = text,
                constraints = constraints,
                onTextLayout = {
                    textLayoutResult = it
                },
                maxLines = maxLines,
            )
            val finalPlaceable = textLayoutResult?.let { layoutResult ->
                if (!layoutResult.didOverflowHeight) return@let initialPlaceable
                val lastVisibleLine = (0 until layoutResult.lineCount)
                    .last {
                        layoutResult.getLineBottom(it) <= layoutResult.size.height
                }
                placeText(
                    text = text,
                    constraints = constraints,
                    onTextLayout = onTextLayout,
                    maxLines = lastVisibleLine + 1,
                )
            } ?: initialPlaceable
    
            layout(
                width = finalPlaceable.width,
                height = finalPlaceable.height
            ) {
                finalPlaceable.place(0, 0)
            }
        }
    }
    

    Usage:

    Card(
        modifier = Modifier
            .height(60.dp)
            .width(100.dp)
            .border(1.dp, Color.Black, RoundedCornerShape(0))
    ) {
        Card(
            modifier = Modifier
                .padding(8.dp)
                .fillMaxSize()
                .border(1.dp, Color.Black, RoundedCornerShape(0))
        ) {
            TextEllipsisFixed(
                text = "One two three four five six seven eight nine ten eleven twelve",
                color = Color.Black
            )
        }
    }
    

    Result: