Search code examples
androidandroid-jetpack-composeandroid-jetpack-compose-text

Align two texts by font's ascent instead of baseline in Jetpack Compose


I know how to align text in Jetpack Compose to the baseline.

But now I would need to align two differently sized texts that follow each other in a Row by the ascent of the larger of these two fonts. I would like to think of this as aligning two texts "by the top baseline" if that makes sense. Modifier.align(Alignment.Top) does not work as it will not align by the ascent but by the layout's top and then the texts are not aligned correctly at the top.

I have tried to look how to do this, but apparently there's no ready made function or modifier for this? I didn't even find a way to access Text's ascent property etc in Compose. So not sure how this would be possible?

Thanks for any hints! :)

Edit: This is what it looks when Alignment.Top is used. But I would like the two texts to align at the top.

enter image description here


Solution

  • All information about text layout can be retrieved with onTextLayout Text argument. In this case you need a line size, which can be retrieved with getLineBottom, and an actual font size, which can be found in layoutInput.style.fontSize.

    I agree that it'd be easier if you could use some simple way to do that, so I've starred your feature request, but for now here's how you can calculate it:

    onTextLayout = { textLayoutResult ->
        val ascent = textLayoutResult.getLineBottom(0) - textLayoutResult.layoutInput.run {
            with(density) {
                style.fontSize.toPx()
            }
        }
    },
    

    Full example of aligning two texts:

    val ascents = remember { mutableStateMapOf<Int, Float>() }
    val texts = remember {
        listOf(
            "Big text" to 80.sp,
            "Small text" to 20.sp,
        )
    }
    Row(
        Modifier
            .drawBehind {
                ascents.maxOfOrNull { it.value }?.let {
                    drawLine(Color.Red, Offset(0f, it), Offset(size.width, it))
                }
            }
    ) {
        texts.forEachIndexed { i, info ->
            Text(
                info.first,
                fontSize = info.second,
                onTextLayout = { textLayoutResult ->
                    ascents[i] = textLayoutResult.getLineBottom(0) - textLayoutResult.layoutInput.run {
                        with(density) {
                            style.fontSize.toPx()
                        }
                    }
                },
                modifier = Modifier
                    .alpha(if (ascents.count() == texts.count()) 1f else 0f)
                    .layout { measurable, constraints ->
                        val placeable = measurable.measure(constraints)
                        val maxAscent = ascents.maxOfOrNull { it.value } ?: 0f
                        val ascent = ascents[i] ?: 0f
                        val yOffset = if (maxAscent == ascent) 0 else (maxAscent - ascent).toInt()
                        layout(placeable.width, placeable.height - yOffset) {
                            placeable.place(0, yOffset)
                        }
                    }
            )
        }
    }
    

    Result:

    enter image description here