androidandroid-jetpack-composeandroid-canvasdrawtextandroid-jetpack-compose-canvas

How to draw text in Jetpack compose at the center of another object?


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.


Solution

  • 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
                )
            }
        }
    }