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)
)
}
Related
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
)
I'm trying to achieve below cardview arc shape on cardview border/stroke.
Already tried to search on google but didn't find any relevant answer that suits with requirement.
Any lead or help will be appriciated.
Answer from Cirilo Bido and Raghunandan is good place to start, you round corners of rectangle with arcTo but you can't draw curved edges on top of clipped out shape. You need to use cubicTo to draw rounded edge and curve to clip out bottom shape
val shape = GenericShape {size: Size, layoutDirection: LayoutDirection ->
// draw cubic on left and right sides for button space
cubicTo()
}
You can check out this answer for drawing with cubic to. By combining both you can draw that path.
Jetpack Compose: How to draw a path / line like this
I created this path based on article shared by
Raghunandan initially, even though that is amazing answer for animating BottomBar it doesn't create a rounded shape if you look closely, at the bottom it's creating a triangular shape at the bottom instead of rounded one and shape OP requires and in article is also different.
So i used sliders to create bezier from the link i shared above. It's available as tutorial here too. Still it can be tweaked to more precise shape if you wish to.
I used x0, y0 as reference point to set control points and created this Path extension function.
fun Path.roundedRectanglePath(
size: Size,
cornerRadius: Float,
fabRadius: Float,
) {
val centerX = size.width / 2
val x0 = centerX - fabRadius * 1.15f
val y0 = 0f
// offset of the first control point (top part)
val topControlX = x0 + fabRadius * .5f
val topControlY = y0
// offset of the second control point (bottom part)
val bottomControlX = x0
val bottomControlY = y0 + fabRadius
// first curve
// set the starting point of the curve (P2)
val firstCurveStart = Offset(x0, y0)
// set the end point for the first curve (P3)
val firstCurveEnd = Offset(centerX, fabRadius * 1f)
// set the first control point (C1)
val firstCurveControlPoint1 = Offset(
x = topControlX,
y = topControlY
)
// set the second control point (C2)
val firstCurveControlPoint2 = Offset(
x = bottomControlX,
y = bottomControlY
)
// second curve
// end of first curve and start of second curve is the same (P3)
val secondCurveStart = Offset(
x = firstCurveEnd.x,
y = firstCurveEnd.y
)
// end of the second curve (P4)
val secondCurveEnd = Offset(
x = centerX + fabRadius * 1.15f,
y = 0f
)
// set the first control point of second curve (C4)
val secondCurveControlPoint1 = Offset(
x = secondCurveStart.x + fabRadius,
y = bottomControlY
)
// set the second control point (C3)
val secondCurveControlPoint2 = Offset(
x = secondCurveEnd.x - fabRadius / 2,
y = topControlY
)
// Top left arc
val radius = cornerRadius * 2
arcTo(
rect = Rect(
left = 0f,
top = 0f,
right = radius,
bottom = radius
),
startAngleDegrees = 180.0f,
sweepAngleDegrees = 90.0f,
forceMoveTo = false
)
lineTo(x = firstCurveStart.x, y = firstCurveStart.y)
// bezier curve with (P2, C1, C2, P3)
cubicTo(
x1 = firstCurveControlPoint1.x,
y1 = firstCurveControlPoint1.y,
x2 = firstCurveControlPoint2.x,
y2 = firstCurveControlPoint2.y,
x3 = firstCurveEnd.x,
y3 = firstCurveEnd.y
)
// bezier curve with (P3, C4, C3, P4)
cubicTo(
x1 = secondCurveControlPoint1.x,
y1 = secondCurveControlPoint1.y,
x2 = secondCurveControlPoint2.x,
y2 = secondCurveControlPoint2.y,
x3 = secondCurveEnd.x,
y3 = secondCurveEnd.y
)
lineTo(x = size.width - cornerRadius, y = 0f)
// Top right arc
arcTo(
rect = Rect(
left = size.width - radius,
top = 0f,
right = size.width,
bottom = radius
),
startAngleDegrees = -90.0f,
sweepAngleDegrees = 90.0f,
forceMoveTo = false
)
lineTo(x = 0f + size.width, y = size.height - cornerRadius)
// Bottom right arc
arcTo(
rect = Rect(
left = size.width - radius,
top = size.height - radius,
right = size.width,
bottom = size.height
),
startAngleDegrees = 0f,
sweepAngleDegrees = 90.0f,
forceMoveTo = false
)
lineTo(x = cornerRadius, y = size.height)
// Bottom left arc
arcTo(
rect = Rect(
left = 0f,
top = size.height - radius,
right = radius,
bottom = size.height
),
startAngleDegrees = 90.0f,
sweepAngleDegrees = 90.0f,
forceMoveTo = false
)
lineTo(x = 0f, y = cornerRadius)
close()
}
Composable that uses this shape
#Composable
private fun CustomArcShape(
modifier: Modifier,
elevation: Dp = 4.dp,
color: Color = MaterialTheme.colorScheme.surface,
contentColor: Color = contentColorFor(color),
content: #Composable () -> Unit
) {
val diameter = 60.dp
val radiusDp = diameter / 2
val cornerRadiusDp = 10.dp
val density = LocalDensity.current
val cutoutRadius = density.run { radiusDp.toPx() }
val cornerRadius = density.run { cornerRadiusDp.toPx() }
val shape = remember {
GenericShape { size: Size, layoutDirection: LayoutDirection ->
this.roundedRectanglePath(
size = size,
cornerRadius = cornerRadius,
fabRadius = cutoutRadius * 2
)
}
}
Spacer(modifier = Modifier.height(diameter / 2))
Box(contentAlignment = Alignment.TopCenter) {
FloatingActionButton(
shape = CircleShape,
containerColor = Color(0xffD32F2F),
modifier = Modifier
.offset(y = -diameter / 5)
.size(diameter)
.drawBehind {
drawCircle(
Color.Red.copy(.5f),
radius = 1.3f * size.width / 2
)
drawCircle(
Color.Red.copy(.3f),
radius = 1.5f * size.width / 2
)
}
.align(Alignment.TopCenter),
onClick = { /*TODO*/ }
) {
Icon(
tint = Color.White,
imageVector = Icons.Filled.Close,
contentDescription = "Close"
)
}
Surface(
modifier = modifier,
shape = shape,
shadowElevation = elevation,
color = color,
contentColor = contentColor
) {
Column {
Spacer(modifier = Modifier.height(diameter))
content()
}
}
}
}
And demonstration
#Composable
private fun CustomArcShapeSample() {
Column(
modifier = Modifier
.fillMaxSize()
) {
CustomArcShape(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth()
.height(250.dp)
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
"Payment Failed",
color = MaterialTheme.colorScheme.error,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(10.dp))
Text("Sorry !", fontSize = 24.sp, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(10.dp))
Text("Your transfer to bank failed", color = Color.LightGray)
}
}
Spacer(modifier = Modifier.height(40.dp))
CustomArcShape(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth()
.height(250.dp)
) {
Column(
modifier = Modifier
.fillMaxSize()
.border(1.dp, Color.Green),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
"Payment Failed",
color = MaterialTheme.colorScheme.error,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(10.dp))
Text("Sorry !", fontSize = 24.sp, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(10.dp))
Text("Your transfer to bank failed", color = Color.LightGray)
}
}
}
}
You probably need to draw that arc in a custom composable, I found this article that can help you to understand the process of drawing in compose!
turtorial article: https://github.com/JeckOnly/passage/blob/master/Android/Widget/Compose/%E9%A1%B6%E9%83%A8%E5%87%B9%E9%99%B7Shape.md
code:https://gist.github.com/JeckOnly/54936415d1670103a4d400f66c8b31a1
hope this can help you though it is in Chinese language.
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)
)
}
}
I've looking to draw an arc on a canvas in Jetpack Compose with a little circle on the edge of progress like this picture:
I found how to draw the progress bar with arc canvas but don't know yet how to draw the circle to match with the edge of the arc line. This is my progress code:
#Composable
fun ComposeCircularProgressBar(
modifier: Modifier = Modifier,
percentage: Float,
fillColor: Color,
backgroundColor: Color,
strokeWidth: Dp
) {
Canvas(
modifier = modifier
.padding(strokeWidth / 2)
) {
// Background Line
drawArc(
color = backgroundColor,
135f,
270f,
false,
style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Butt)
)
// Fill Line
drawArc(
color = fillColor,
135f,
270f * percentage,
false,
style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Round)
)
}
}
Noted: for now I know to draw that circle is with Canvas.drawCircle(offset = Offset) but I don't know yet how to calculate the Offset(x,y) to match with the edge of progress.
This piece of code below will generate the arc with the circular dot based on the percentage you provide. You did get most of the part right, it was just about solving the Math Equation to find the point on the circle.
I assumed the radius of the circle as Height / 2 of the widget.
Since we are not drawing the full circle, the start angle is at 140 degrees and the maximum sweep angle is 260 degrees. (I found this by hit and trial, so that it looks as close to your image)
Now to draw the small white circle the center a.k.a offset has to be at (x,y) where x & y are given by the formula
x = radius * sin (angle in radians)
y = radius * cos (angle in radians)
#Composable
fun ComposeCircularProgressBar(
modifier: Modifier = Modifier,
percentage: Float,
fillColor: Color,
backgroundColor: Color,
strokeWidth: Dp
) {
Canvas(
modifier = modifier
.size(150.dp)
.padding(10.dp)
) {
// Background Line
drawArc(
color = backgroundColor,
140f,
260f,
false,
style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Round),
size = Size(size.width, size.height)
)
drawArc(
color = fillColor,
140f,
percentage * 260f,
false,
style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Round),
size = Size(size.width, size.height)
)
var angleInDegrees = (percentage * 260.0) + 50.0
var radius = (size.height / 2)
var x = -(radius * sin(Math.toRadians(angleInDegrees))).toFloat() + (size.width / 2)
var y = (radius * cos(Math.toRadians(angleInDegrees))).toFloat() + (size.height / 2)
drawCircle(
color = Color.White,
radius = 5f,
center = Offset(x, y)
)
}
}
Here are some examples I tried with
#Preview
#Composable
fun PreviewPorgressBar() {
ComposeCircularProgressBar(
percentage = 0.80f,
fillColor = Color(android.graphics.Color.parseColor("#4DB6AC")),
backgroundColor = Color(android.graphics.Color.parseColor("#90A4AE")),
strokeWidth = 10.dp
)
}
#Preview
#Composable
fun PreviewPorgressBar() {
ComposeCircularProgressBar(
percentage = 0.45f,
fillColor = Color(android.graphics.Color.parseColor("#4DB6AC")),
backgroundColor = Color(android.graphics.Color.parseColor("#90A4AE")),
strokeWidth = 10.dp
)
}
#Preview
#Composable
fun PreviewPorgressBar() {
ComposeCircularProgressBar(
percentage = 1f,
fillColor = Color(android.graphics.Color.parseColor("#4DB6AC")),
backgroundColor = Color(android.graphics.Color.parseColor("#90A4AE")),
strokeWidth = 10.dp
)
}
[Update] If you're interested in a step-by-step tutorial you can read it here :
https://blog.droidchef.dev/custom-progress-with-jetpack-compose-tutorial/
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))