Jetpack compose canvas clip to rounded arc shape - android

I have the following two overlaying 360 degrees arc (essentially two circles) clipped by an arc shape. Unlike drawArc, addArc does not take into style = ... which can be used to define cap = StrokeCap.Round. The current clip looks as expected, an arc clipped with hard edges. Is there a way to round out the corners??
I tried using blendMode, but that does not work as expected since the color of the layers could be diffrent opacity, which blend mode takes in consideration when blending.
Canvas(modifier = Modifier.fillMaxSize()) {
val path = Path().apply {
this.addArc(
oval = Rect(
topLeft = Offset(0F, 0F),
bottomRight = Offset(size.width, size.height)
),
startAngleDegrees = 0F,
sweepAngleDegrees = 240F
)
}
clipPath(path = path) {
// following two drawArc could be replaced w/ drawCircle
drawArc(
color = Color.Magenta.copy(alpha = 0.2F),
startAngle = 0f,
sweepAngle = 360f,
useCenter = false,
style = Stroke(strokeWidthPx2, cap = StrokeCap.Round),
size = Size(size2, size2),
topLeft = Offset(offset2, offset2)
)
drawArc(
color = Color.Cyan,
startAngle = 0f,
sweepAngle = 360f,
useCenter = false,
style = Stroke(strokeWidthPx, cap = StrokeCap.Round),
size = Size(size1, size1),
topLeft = Offset(offset, offset)
)
}
}

Related

How to make rounded corner in drawLine in jetpack compose

I am using Canvas Api to draw something. I want to draw line with rounded corner. Line is draw without any problem. But I cannot figure out which attribute for corner radius.
val boxSize = 30.dp
Box(modifier = Modifier
.background(Color.LightGray)
.height(height = boxSize)
) {
Canvas(
modifier = Modifier
.fillMaxWidth()
) {
val canvasWidth = size.width
drawLine(
start = Offset(x = 0f, y = (boxSize / 2).toPx()),
end = Offset(x = canvasWidth, y = (boxSize / 2).toPx()),
color = Color.Black,
strokeWidth = 8.dp.toPx()
)
}
}
My view is simple without corner radius.
I want my Black line to be corner for each side with specific radius.
You need to add the cap argument to drawLine and set it to StrokeCap.Round.
drawLine(
start = Offset(x = 0f, y = (boxSize / 2).toPx()),
end = Offset(x = canvasWidth, y = (boxSize / 2).toPx()),
color = Color.Black,
strokeWidth = 8.dp.toPx(),
cap = StrokeCap.Round, //add this line for rounded edges
)

Android: compose drawArc would not get centered within `Box`

I am trying to show progress using drawArc for compose. I have tried drawBehind modifier to draw a background circle and not trying to draw another circle on top of it to show the progress. The problems is, no matter what I try, the arc turns out to be drawn at top left corner all the time. If I explicitly define the topLeft value, it only works for 1 screen size. So I am trying to get a dynamic size for both circles and also the strokewidth. So for Tablets only the circles should be increased depending on the screensize and also the thickness should be increased for the same. And for the smaller size devices, the value should be decreasing. Here is my code and output:
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
BoxWithConstraints(
Modifier
.fillMaxSize()
.background(Color.Yellow),
) {
Canvas(modifier = Modifier
.size(maxWidth, maxHeight)
.background(color = Color.Red)) {
drawCircle(
color = Color.Gray,
radius = (maxWidth /4).toPx(),
style = Stroke(width = 14f, cap = StrokeCap.Round),
)
val sweepAngle = progress/100 * 360
drawArc(
size = Size((maxWidth/2).toPx(),(maxWidth/2).toPx()),
color = Color.Green,
startAngle = -90f,
sweepAngle = sweepAngle,
useCenter = false,
style = Stroke(10f, cap = StrokeCap.Round),
)
}
}
}
PS: I cannot use another circle object since I need to have the tip of the circle round aka cap should be Stroke.Round.
I ended up trying BoxWithConstraints so I can have access to maxWidth and maxHeight
You have to calculate the topLeft offset of the arc as center of the circle - radius of the circle.
Then the size of arc is maxWidth/2 instead of maxWidth/4 = radius.
Something like:
val stroke = 5.dp
drawCircle(
color = Color.Gray,
radius = (maxWidth /4).toPx(),
style = Stroke(width = stroke.toPx(), cap = StrokeCap.Round),
)
val sweepAngle = progress/100 * 360
val offsetx = this.center.x - (maxWidth/4).toPx()
val offsety = this.center.y - (maxWidth/4).toPx()
drawArc(
size = Size((maxWidth/2).toPx(),(maxWidth/2).toPx()),
color = Color.Green,
startAngle = 0f,
sweepAngle = -sweepAngle,
topLeft = Offset(offsetx,offsety),
useCenter = false,
style = Stroke(stroke.toPx(), cap = StrokeCap.Round),
)
}

How to attain UI like this with Jetpack Compose?

I've created an Arc with canvas like this:
Canvas(modifier = Modifier.size(255.dp), onDraw = {
rotate(300f) {
drawArc(
color = primary,
startAngle = 0f,
sweepAngle = 180f,
useCenter = false,
style = Stroke(width = 8f)
)
}
})
But, that canvas composable draws in a square shape so that I can't make the edges of arc to touch border of the screen.
Use topLeft = Offset(x, y) in the drawArc function with negative values for x and y if necessary to translate it where you need it.

How to cut rect from circle in Compose Canvas?

When I use BlendMode.DstOut, it's supposed to show transparent source shape but it's black.
Supposed result:
Real result:
#Composable
fun BlendMode() {
Box(modifier = Modifier.fillMaxSize().background(White100))
Canvas(modifier = Modifier.size(300.dp)) {
val radius = size.width / 3
drawCircle(
color = Color.Red,
radius = radius,
center = Offset(radius, radius)
)
drawRect(
color = Color.Blue,
topLeft = Offset(radius, radius),
size = Size(radius * 2, radius * 2),
blendMode = BlendMode.DstOut
)
}
}
The blendMode will not allow you to achieve the desired result. You can do this by using clipRect:
clipRect(
top = radius,
left = radius,
right = radius * 3,
bottom = radius * 3,
clipOp = ClipOp.Difference
) {
drawCircle(
color = Color.Red,
radius = radius,
center = Offset(radius, radius)
)
}

Animating circle drawing in Jetpack compose

I am trying to animate circle drawing using drawCircle on Canvas as follows:
drawCircle(
color = Color.Black,
radius = 200f * animatableCircle.value,
center = Offset(size.width / 4, size.height / 4),
style = Stroke(2f)
)
It doesn't look like circle is being drawn, instead the circle starts to scale from the centre. Is it possible to achieve circle drawing effect as in along the radius similar to CircularProgressIndicator as shown?
Just to complete the code posted by #Varsha Kulkarni: (+1)
val radius = 200f
val animateFloat = remember { Animatable(0f) }
LaunchedEffect(animateFloat) {
animateFloat.animateTo(
targetValue = 1f,
animationSpec = tween(durationMillis = 3000, easing = LinearEasing))
}
Canvas(modifier = Modifier.fillMaxSize()){
drawArc(
color = Color.Black,
startAngle = 0f,
sweepAngle = 360f * animateFloat.value,
useCenter = false,
topLeft = Offset(size.width / 4, size.height / 4),
size = Size(radius * 2 ,
radius * 2),
style = Stroke(2.0f))
}
Using drawArc as follows,
drawArc(
color = Color.Black,
startAngle = 0f,
sweepAngle = 360f * animatableCircle.value,
useCenter = false,
topLeft = Offset(size.width / 4, size.height / 4),
size = Size(CIRCLE_RADIUS * 2 ,
CIRCLE_RADIUS * 2),
style = Stroke(2.0f))

Categories

Resources