Ghasem Shirdel
Ghasem Shirdel

Reputation: 567

Cutting one shape from an other shape in Jetpack Compose

I had a question about making this view in Compose and I have no idea about implementing it.

view

My current code looks like this:

Box(
    modifier = Modifier
        .fillMaxSize()
        .height(300.dp)
) {
    Canvas(modifier = Modifier.matchParentSize()) {
        drawRoundRect(
            color = Color.Yellow,
            cornerRadius = CornerRadius(16.dp.toPx(), 16.dp.toPx())
        )
        drawRoundRect(
            color = Color.White,
            topLeft = Offset(
                x = size.width / 5,
                y = size.height - 60.dp.toPx()
            ),
            size = Size((size.width / 5) * 3, 50.dp.toPx() * 2),
            cornerRadius = CornerRadius(24.dp.toPx(), 24.dp.toPx()),
        )
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text(
            text = "Test",
            modifier = Modifier.align(Alignment.BottomCenter)
        )
    }
}

Result is the following:

my view

Upvotes: 4

Views: 3775

Answers (2)

Gregor B.
Gregor B.

Reputation: 142

Also you can use a custom Shape for your Composables to give them a specific outline. Just extend the Shape interface and override the createOutline() method.

Example:

For the corners, the Path API offers a function arcTo(). Then, to draw the edges of the shape, use the lineTo() method.

class RoundedRectOutlinedCorner(
    private val cornerRadius: Dp = 16.dp,
    private val cutOutHeight: Dp = 60.dp,
    private val cutOutWidth: Dp = 145.dp
) : Shape {
    override fun createOutline(
        size: Size, layoutDirection: LayoutDirection, density: Density
    ): Outline {
        return Outline.Generic(Path().apply {
            val cornerRadius = with(density) { cornerRadius.toPx() }
            val cutOutHeight = with(density) { cutOutHeight.toPx() }
            val cutOutWidth = with(density) { cutOutWidth.toPx() }

            arcTo(
                rect = Rect(offset = Offset(0f, 0f), Size(cornerRadius, cornerRadius)),
                startAngleDegrees = 180f,
                sweepAngleDegrees = 90f,
                forceMoveTo = false
            )

            lineTo(size.width - cutOutWidth - cornerRadius, 0f)
            arcTo(
                rect = Rect(
                    offset = Offset(size.width - cutOutWidth - cornerRadius, 0f),
                    Size(cornerRadius, cornerRadius)
                ), startAngleDegrees = 270.0f, sweepAngleDegrees = 90f, forceMoveTo = false
            )

            lineTo(size.width - cutOutWidth, cutOutHeight - cornerRadius)
            arcTo(
                rect = Rect(
                    offset = Offset(size.width - cutOutWidth, cutOutHeight - cornerRadius),
                    Size(cornerRadius, cornerRadius)
                ), startAngleDegrees = 180.0f, sweepAngleDegrees = -90f, forceMoveTo = false
            )

            lineTo(size.width - cornerRadius, cutOutHeight)
            arcTo(
                rect = Rect(
                    offset = Offset(size.width - cornerRadius, cutOutHeight),
                    Size(cornerRadius, cornerRadius)
                ), startAngleDegrees = 270f, sweepAngleDegrees = 90f, forceMoveTo = false
            )

            lineTo(size.width, size.height - cornerRadius)
            arcTo(
                rect = Rect(
                    offset = Offset(size.width - cornerRadius, size.height - cornerRadius),
                    Size(cornerRadius, cornerRadius)
                ), startAngleDegrees = 0f, sweepAngleDegrees = 90f, forceMoveTo = false
            )

            lineTo(cornerRadius, size.height)
            arcTo(
                rect = Rect(
                    offset = Offset(0f, size.height - cornerRadius),
                    Size(cornerRadius, cornerRadius)
                ), startAngleDegrees = 90f, sweepAngleDegrees = 90f, forceMoveTo = false
            )
            close()
        })
    }
}

Usage: Then, you can clip a shape with:

Modifier
    .height(250.dp)
    .clip(RoundedRectOutlinedCorner()),

Or with .graphicsLayer/.background etc.

Result:

enter image description here

Upvotes: 0

Phil Dukhov
Phil Dukhov

Reputation: 88142

To clip some path from an other path, you can use clipPath.

And to outer corner radius, you need to add arcs to the path manually, like this:

Canvas(modifier = Modifier.matchParentSize()) {
    val outerCornerRadius = 16.dp.toPx()
    val clippedPath = Path().apply {
        val innerCornerRadius = 24.dp.toPx()
        val rectSize = Size(round((size.width / 5) * 3), round(50.dp.toPx() * 2))
        val rect = Rect(
            offset = Offset(
                x = (size.width - rectSize.width) / 2,
                y = size.height - rectSize.height
            ),
            size = rectSize
        )

        addRoundRect(
            RoundRect(
                rect,
                topLeft = CornerRadius(x = innerCornerRadius, y = innerCornerRadius),
                topRight = CornerRadius(x = innerCornerRadius, y = innerCornerRadius),
            )
        )
        val outerCornerDiameter = outerCornerRadius * 2
        val cornerSize = Size(outerCornerDiameter,outerCornerDiameter)
        val cornerOffset = Offset(outerCornerDiameter, outerCornerDiameter)
        val cornerYOffset = Offset(0f, outerCornerDiameter)
        moveTo(rect.bottomLeft - cornerYOffset)
        addArc(
            Rect(
                offset = rect.bottomLeft - cornerOffset,
                size = cornerSize
            ),
            startAngleDegrees = 0f,
            sweepAngleDegrees = 90f,
        )
        lineTo(rect.bottomLeft)

        moveTo(rect.bottomRight - cornerYOffset)
        addArc(
            Rect(
                offset = rect.bottomRight - cornerYOffset,
                size = cornerSize
            ),
            startAngleDegrees = 180f,
            sweepAngleDegrees = -90f,
        )
        lineTo(rect.bottomRight)
    }
    clipPath(clippedPath, clipOp = ClipOp.Difference) {
        drawRoundRect(
            color = Color.Yellow,
            cornerRadius = CornerRadius(outerCornerRadius, outerCornerRadius)
        )
    }
}

Result:

Upvotes: 8

Related Questions