Home > Software engineering >  Jetpack Compose - Draw Arc with Pattern
Jetpack Compose - Draw Arc with Pattern

Time:11-20

I've looking to draw an arc on a canvas in Jetpack Compose with a diagonally striped pattern like this picture:

enter image description here

I'm looking to use it as a progress bar, so it can be extended or shortened based on the percentage completed. I currently have a custom circular progress bar where I draw a circle on the canvas for the empty portion of the progress and then I draw an arc for completed progress. However, design wants to implement an overage state where, instead of a solid the color, the overage has this striped pattern.

Right now the workaround I have is just to draw a rectangle filled with stripes like this and then clip the corners, then putting a white circle in the middle to make it appear like a ring, then overlaying another progress bar on top and removing solid color as needed in reverse to reveal the stripes. However, this doesn't give me much freedom to work with the overage. Mainly the curved white gap becomes tough to accomplish, I've only been able to do a squared corner gap.

So I'm looking to create the arc with this striped pattern.

CodePudding user response:

You can achieve this striped pattern using Brush.linearGradient.
The trick here is to use "sharp" color stops. So we would set the same position for where transparent color should stop and for where the stripe color should start.

This allows to create an angled linear gradient that will looks like this on a small square:

enter image description here

but when used on bigger area with TileMode.Repeated it would "loop" nicely into desired striped pattern:

enter image description here

Here is a sample on how to achieve such gradient:

private fun Density.createStripeBrush(
    stripeColor: Color,
    stripeWidth: Dp,
    stripeToGapRatio: Float
): Brush {
    val stripeWidthPx = stripeWidth.toPx()
    val stripeGapWidthPx = stripeWidthPx / stripeToGapRatio
    val brushSizePx = stripeGapWidthPx   stripeWidthPx
    val stripeStart = stripeGapWidthPx / brushSizePx

    return Brush.linearGradient(
        stripeStart to Color.Transparent,
        stripeStart to stripeColor,
        start = Offset(0f, 0f),
        end = Offset(brushSizePx, brushSizePx),
        tileMode = TileMode.Repeated
    )
}

Having a Brush like that we can use it with any drawing operation.
For example here it's used in Modifier.drawWithCache to drawArc, so you have full freedom to control the stroke style etc.:

.drawWithCache {
    val brush = createStripeBrush(
        stripeColor = Color.Blue,
        stripeWidth = stripeWidth,
        stripeToGapRatio = 1.8f
    )
    onDrawWithContent {
        drawArc(
            brush = brush,
            startAngle = 0f,
            sweepAngle = -90f,
            useCenter = false,
            style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
        )
    }
}

enter image description here

CodePudding user response:

Your solution looks correct. To get the rounded gap, try creating that gap with an image and rotating it around the circle. The gap image is rounded on both ends. The interior of the image is white but the exterior is transparent - essentially a png or possibly a svg.

Alternatively, create an image that has a white circle (the big one you are already using) with a gap extending on its edge and then just rotate the circle to have the gap appear where it should.

  • Related