Search code examples
androidandroid-jetpack-composeshapesandroid-jetpackandroid-jetpack-compose-canvas

Generic shape in Jetpack compose


How to draw a generic shape with Jetpack compose?

I want to draw roundedCornerCircle but the right side should have an inside curve instead of an outside curve.

I tried to give a negative radius in roundedCornerShape but it is not working. Can someone help me to understand how to customize this and draw this in Android Jetpack compose?

Ignore this rectangle in middle. enter image description here


Solution

  • Result

    enter image description here

    If you want this as a Shape to be clickable inside boundaries of this custom shape you can create a RoundedRectangle with corner on left side only using

    val shape = GenericShape { size: Size, layoutDirection: LayoutDirection ->
        val width = size.width
        val height = size.height
    
        addArc(
            oval = Rect(
                offset = Offset.Zero,
                size = Size(height, height)
            ),
            startAngleDegrees = 90f,
            sweepAngleDegrees = 180f,
        )
        lineTo(width, 0f)
        lineTo(width, height)
        lineTo(height, height)
    
    }
    

    Then to clip right side you can use BlendMode as in this q&a

    How to clip or cut a Composable?

    Box(
        modifier = Modifier
            .size(200.dp, 100.dp)
            .clip(shape)
            .drawWithContent {
    
                val width = size.width
                val height = size.height
                val radius = 30f
    
    
                with(drawContext.canvas.nativeCanvas) {
                    val checkPoint = saveLayer(null, null)
                    // Destination
                    drawContent()
    
                    // Source
                    drawArc(
                        color = Color.Transparent,
                        startAngle = 90f,
                        sweepAngle = 180f,
                        topLeft = Offset(
                            width - radius, 0f
                        ),
                        size = Size(2 * radius, height),
                        useCenter = false,
                        blendMode = BlendMode.SrcOut
                    )
                    restoreToCount(checkPoint)
                }
            }
            .background(Yellow400)
            .clickable {
    
            }
    )
    

    Full implementation

    @Preview
    @Composable
    fun ShapePreview() {
    
        Column(modifier = Modifier
            .fillMaxSize()
            .padding(20.dp)) {
    val shape = GenericShape { size: Size, layoutDirection: LayoutDirection ->
        val width = size.width
        val height = size.height
    
        addArc(
            oval = Rect(
                offset = Offset.Zero,
                size = Size(height, height)
            ),
            startAngleDegrees = 90f,
            sweepAngleDegrees = 180f,
        )
        lineTo(width, 0f)
        lineTo(width, height)
        lineTo(height, height)
    
    }
    
            Box(
                modifier = Modifier
                    .size(200.dp, 100.dp)
                    .clip(shape)
                    .drawWithContent {
    
                        val width = size.width
                        val height = size.height
                        // Change this as required, ra
                        val radius = 80f
    
    
                        with(drawContext.canvas.nativeCanvas) {
                            val checkPoint = saveLayer(null, null)
                            // Destination
                            drawContent()
    
                            // Source
                            drawArc(
                                color = Color.Transparent,
                                startAngle = 90f,
                                sweepAngle = 180f,
                                topLeft = Offset(
                                    width - radius, 0f
                                ),
                                size = Size(2 * radius, height),
                                useCenter = false,
                                blendMode = BlendMode.SrcOut
                            )
                            restoreToCount(checkPoint)
                        }
                    }
                    .background(Orange400)
                    .clickable {
    
                    }
            )
        }
    }
    

    If you only need to draw it in DrawScope you don't need the Shape at all. Create a RoundedRect with left rounded side and do blend mode for the right side.