I've looking to draw an arc on a canvas in Jetpack Compose with a diagonally striped pattern like this picture:
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:
but when used on bigger area with TileMode.Repeated
it would "loop" nicely into desired striped pattern:
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)
)
}
}
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.