Jetpack Compose semicircle using Canvas

Issue

I am trying to create a semicircle speed progress bar in Jetpack Compose. Unless the view is square the semicircle will not look as expected, if I use 1:2 width: height it will be flattened. I want a Composable representing half of the circle where I don’t have unusable bottom half of the view.

    Box(
        modifier = modifier
            .background(Color.Red)
    ) {
        Canvas(modifier = Modifier.size(300.dp)) {
            drawArc(
                color = Color.LightGray,
                -180f,
                180f,
                useCenter = false,
                style = Stroke(8.dp.toPx(), cap = StrokeCap.Round)
            )
        }
        Text(
            modifier = Modifier.align(alignment = Alignment.Center),
            text = "20 Mbps",
            color = Color.White,
            fontSize = 20.sp
        )
    }

enter image description here

The expected outcome would be a reusable semicircle composable with a height of the actual semicircle so I can easily position other content against it. The expected view size is marked by a dotted green line.

enter image description here

Solution

As i mentioned in comments arc uses rectangle if you want a semi arc that covers whole hight just double the height you draw arc with

@Composable
private fun ArcComposable(modifier: Modifier) {
    Box(
        modifier = modifier
            .background(Color.Red)
    ) {
        Canvas(modifier = Modifier
            .size(300.dp)
            .clipToBounds()) {
            drawArc(
                color = Color.LightGray,
                -180f,
                180f,
                useCenter = false,
                size = Size(size.width, size.height * 2),
                style = Stroke(8.dp.toPx(), cap = StrokeCap.Round)
            )
        }
        Text(
            modifier = Modifier.align(alignment = Alignment.Center),
            text = "20 Mbps",
            color = Color.White,
            fontSize = 20.sp
        )
    }
}

I added Modifier.clipToBounds() because of strokeCap round which is added to length of the line by default. You can just reduce size and height few px to match inside the canvas. Canvas by default even if you don’t set a modifier with size it draws anything out of its bounds unless you use Modifier.clipToBounds()

enter image description here

private fun ArcComposable(modifier: Modifier) {
    Box(
        modifier = modifier
            .background(Color.Red)
    ) {
        Canvas(
            modifier = Modifier
                .size(300.dp)
//            .clipToBounds()
        ) {
            drawArc(
                color = Color.LightGray,
                -180f,
                180f,
                useCenter = false,
                topLeft = Offset(4.dp.toPx(), 6.dp.toPx()),
                size = Size(size.width - 8.dp.toPx(), size.height * 2 - 20.dp.toPx()),
                style = Stroke(8.dp.toPx(), cap = StrokeCap.Round)
            )
        }
        Text(
            modifier = Modifier.align(alignment = Alignment.Center),
            text = "20 Mbps",
            color = Color.White,
            fontSize = 20.sp
        )
    }
}

Answered By – Thracian

Answer Checked By – Katrina (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *