2

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

Johan Paul
  • 1,902
  • 2
  • 16
  • 33

2 Answers2

2

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

Pylyp Dukhov
  • 38,521
  • 10
  • 57
  • 92
  • 1
    Thanks Philip for the answer! I really appreciate you taking the time and making a working sample out of this! This indeed seems to work fine - I will adopt your solution :) This is a working solution - which is nice - but hopefully the Jetpack Compose team can provide this out of the box. And I didn't know how to get the ascent, so thanks for that also! – Johan Paul Jan 10 '22 at 14:40
0

One workaround would be you can adjust y-axis offset modifier according to your need.

Text(text = "Second", modifier = Modifier.offset(x = 0.dp, y = 5.dp))

you can have negative value for offset as well if you like to up your first text according to your need.

  • 1
    Yes, that would be one option. But feels hackish to me and the value would depend on the font used. This would be the last resort and workaround that I would implement if there is no proper solution for this in Compose. Which to be honest would be a surprise if it does not exist. And I would probably file a bug for it. – Johan Paul Dec 30 '21 at 13:51