Search code examples
androidkotlinandroid-canvasandroid-jetpack-composeandroid-jetpack

Wavy box in Jetpack compose


Is there a way to make a box with a wavy top with Canvas?

I would like to know if this effect can be achieved directly with a Canvas, it is not necessary to have a scrolling animation.

enter image description here


Solution

  • It's not quite clear why you're talking about Canvas. To crop a view like this, you can use a custom Shape and apply it to your view with Modifier.clip. Here's a shape you can use:

    class WavyShape(
        private val period: Dp,
        private val amplitude: Dp,
    ) : Shape {
        override fun createOutline(
            size: Size,
            layoutDirection: LayoutDirection,
            density: Density,
        ) = Outline.Generic(Path().apply {
            val wavyPath = Path().apply {
                val halfPeriod = with(density) { period.toPx() } / 2
                val amplitude = with(density) { amplitude.toPx() }
                moveTo(x = -halfPeriod / 2, y = amplitude)
                repeat(ceil(size.width / halfPeriod + 1).toInt()) { i ->
                    relativeQuadraticBezierTo(
                        dx1 = halfPeriod / 2,
                        dy1 = 2 * amplitude * (if (i % 2 == 0) 1 else -1),
                        dx2 = halfPeriod,
                        dy2 = 0f,
                    )
                }
                lineTo(size.width, size.height)
                lineTo(0f, size.height)
            }
            val boundsPath = Path().apply {
                addRect(Rect(offset = Offset.Zero, size = size))
            }
            op(wavyPath, boundsPath, PathOperation.Intersect)
        })
    }
    

    If you really need to use this inside Canvas for some reason, you can pass the same Path that I create inside WavyShape to DrawScope.clipPath, so that the contents of the clipPath block will be clipped.

    Apply custom shape to your Image or any other view:

    Image(
        painter = painterResource(id = R.drawable.my_image_1),
        contentDescription = null,
        contentScale = ContentScale.FillBounds,
        modifier = Modifier
            .clip(WavyShape(period = 100.dp, amplitude = 50.dp))
    )
    

    Result: