JetPack Compose — How To Draw Rectangle With Circular Cut Out

דרור זוהר
3 min readJun 21, 2024

Jetpack Compose, the modern UI toolkit for Android, allows developers to create complex custom shapes with ease. In this article, we’ll walk through creating a custom shape: a rectangle with a circular cutout. We’ll define this shape in Kotlin using Jetpack Compose’s Shape interface.

Step-by-Step Guide

To create a rectangle with a circular cutout, we’ll define a custom shape by implementing the Shape interface. This involves overriding the createOutline function to define the shape's path.

1. Define the Custom Shape Class

First, create a class called RectangleWithCircularCutOut that implements the Shape interface. This class will take two parameters: cornerRadius and cutCornerRadius, which define the sizes of the corners and the cutout,

class RectangleWithCircularCutOut(
private val cornerRadiusInDp: Dp = 0.dp,
private val cutCornerRadiusInDp: Dp,
) : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline {
val cornerRadius = with(density) { cornerRadiusInDp.toPx() }
val cutCornerRadius = with(density) { cutCornerRadiusInDp.toPx() }

return Outline.Generic(Path().apply {
reset()

// 1. Move to the starting point
moveTo(cornerRadius, 0f)

// 2. Draw the top-end corner arc
arcTo(
rect = Rect(
offset = Offset(size.width - cornerRadius, 0f),
size = Size(cornerRadius, cornerRadius)
),
startAngleDegrees = 270f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)

// 3. Draw the circular cutout arc
arcTo(
rect = Rect(
offset = Offset(size.width - cutCornerRadius, size.height - cutCornerRadius),
size = Size(cutCornerRadius*2, cutCornerRadius*2)
),
startAngleDegrees = 0f,
sweepAngleDegrees = -270f,
forceMoveTo = false
)

// 4. Draw the bottom-start corner arc
arcTo(
rect = Rect(
offset = Offset(0f, size.height - cornerRadius),
size = Size(cornerRadius, cornerRadius)
),
startAngleDegrees = 90f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)

// 5. Draw the top-start corner arc
arcTo(
rect = Rect(
offset = Offset(0f, 0f),
size = Size(cornerRadius, cornerRadius)
),
startAngleDegrees = 180f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)

// 9. Close the path to complete the shape
close()
})
}
}

2. Using the Custom Shape in Jetpack Compose

Now that we have defined our custom shape, we can use it in our Jetpack Compose UI. Here’s an example of how to apply this shape to a Box composable.

@Composable
fun CustomShapeDemo() {
Box(
modifier = Modifier
.size(60.dp)
.clip(RectangleWithCircularCutOut(cornerRadiusInDp = 4.dp, cutCornerRadiusInDp = 16.dp))
.background(Color.Blue)
)
}

@Preview
@Composable
fun PreviewCustomShapeDemo() {
CustomShapeDemo()
}

In this example, we create a Box with a size of 60dp, clip it to our RectangleWithCircularCutOut shape, and set its background color to blue.

Explanation of the Path Drawing

  1. Starting Point: We start by moving to the initial point (cornerRadius, 0f).
  2. Top-Right Corner Arc: We draw an arc from the top-right corner using the arcTo function.
  3. Bottom-Right Corner: Draw a line to the bottom-right corner.
  4. Circular Cutout Arc: Draw an arc to create the circular cutout.
  5. Bottom-Left Corner: Draw a line to the bottom-left corner.
  6. Bottom-Left Corner Arc: Draw an arc for the bottom-left corner.
  7. Top-Left Corner: Draw a line to the top-left corner.
  8. Top-Left Corner Arc: Draw an arc for the top-left corner.
  9. Closing the Path: Close the path to complete the shape.

Conclusion

Creating custom shapes in Jetpack Compose can seem daunting, but with a solid understanding of the Path and Shape APIs, you can create intricate and unique UI components. The RectangleWithCircularCutOut shape demonstrates how to combine lines and arcs to form a custom shape that can be reused across your Compose application.

Experiment with different corner and cutout sizes to create various visual effects and enhance your app’s UI design. Happy coding!

--

--