1

I am trying to draw text inside a circle in Jetpack Compose.

I drew the circle with the center being the center of the canvas.

Now I want to draw text exactly at the center of the circle.

The drawText function inside the canvas in Jetpack compose has topLeft parameter which expects the Offset of the top left corner of the rectangle holding the text. I know I can't pass the center of the circle as the topLeft offset, and if I do, the result will look like this, which is not what I want.

trying to draw the text at the center of the circle

I want the text to be drawn exactly at the center of the Circle.

Here is the code:

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(Color.Green.copy(alpha = 0.2f))
        .padding(36.dp),
    contentAlignment = Alignment.Center
) {
    val textMeasurer = rememberTextMeasurer()
    val textToDraw = "A"
    Canvas(modifier = Modifier.fillMaxSize()) {
        drawCircle(
            center = Offset(
                x = center.x,
                y = center.y
            ),
            radius = 350f,
            color = Color.Blue,
            style = Stroke(
                width = 8f
            )
        )

        drawText(
            textMeasurer = textMeasurer,
            text = textToDraw,
            style = TextStyle(
                fontSize = 150.sp,
                color = Color.Black,
                background = Color.Red.copy(alpha = 0.2f)
            ),
            topLeft = Offset(
                x = center.x,
                y = center.y
            )
        )
        drawPoints(
            points = listOf(Offset(center.x, center.y)),
            pointMode = PointMode.Points,
            cap = StrokeCap.Round,
            color = Color.Red,
            strokeWidth = 25f
        )
    }
}

I can manually adjust the offset like this to make it work but this is not optimal and automatic.

topLeft = Offset(
    x = center.x - 125,
    y = center.y - 270
),

text centered in the circle

How can this be accomplished? Appreciate any help.

Note: The light red background I applied for the text is just to visualize the underlying rectangle being used for the text. Also I drew a point at the center of the circle to make more clear.

skafle
  • 627
  • 1
  • 6
  • 16

1 Answers1

2

You can get TextLayoutResult which returns size of the rectangle that text is contained. Andy by offsetting half of the width to left and half of the height to top you can center text inside Canvas.

enter image description here

val textMeasurer = rememberTextMeasurer()

val textToDraw = "A"

val style = TextStyle(
    fontSize = 150.sp,
    color = Color.Black,
    background = Color.Red.copy(alpha = 0.2f)
)
// Keys are to demonstrate that you can re-calculate block inside
// when any of them change. Otherwise remember results on each recomposition
val textLayoutResult = remember(textToDraw, style) {
    textMeasurer.measure(textToDraw, style)
}

Full Sample

@Preview
@Composable
private fun DrawTextAtCenterSample() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Green.copy(alpha = 0.2f))
            .padding(36.dp),
        contentAlignment = Alignment.Center
    ) {
        val textMeasurer = rememberTextMeasurer()

        val textToDraw = "A"

        val style = TextStyle(
            fontSize = 150.sp,
            color = Color.Black,
            background = Color.Red.copy(alpha = 0.2f)
        )

        val textLayoutResult = remember(textToDraw) {
            textMeasurer.measure(textToDraw, style)
        }

        Canvas(modifier = Modifier.fillMaxSize()) {
            drawCircle(
                center = Offset(
                    x = center.x,
                    y = center.y
                ),
                radius = 350f,
                color = Color.Blue,
                style = Stroke(
                    width = 8f
                )
            )


            drawText(
                textMeasurer = textMeasurer,
                text = textToDraw,
                style = style,
                topLeft = Offset(
                    x = center.x - textLayoutResult.size.width / 2,
                    y = center.y - textLayoutResult.size.height / 2,
                )
            )
            drawPoints(
                points = listOf(Offset(center.x, center.y)),
                pointMode = PointMode.Points,
                cap = StrokeCap.Round,
                color = Color.Red,
                strokeWidth = 25f
            )
        }
    }
}
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • 1
    Actually, the text itself doesn't stay at the perfect center of the text-rectangle. It stays little lower than the center of the rectangle. So, if we Increase the fontsize to bigger enough to touch the circle, then we see the difference that the text touches the circle in it's lower part first. That is separate topic though but any insights is appreciated. Thanks again. – skafle Apr 23 '23 at 12:20
  • 1
    You are right. Even after removing font padding and setting line height as [in this link](https://android-developers.googleblog.com/2022/05/whats-new-in-jetpack-compose.html) it doesn't align perfectly. I checked out other measurement result functions in textLayoutResult but they don't return as well. Maybe calculation with baseline helps. You should ask this as a separate question and hopefully find an answer which is very important to align text in canvas. – Thracian Apr 23 '23 at 13:16
  • I also checked this with `Text`'s `onLayout` callback it returns the same values, wonder what causes this small offset – Thracian Apr 23 '23 at 13:46
  • 1
    I did ask another question for this particular issue: https://stackoverflow.com/questions/76086493/drawing-text-in-jetpack-compose-with-precision – skafle Apr 23 '23 at 18:12