Image for post
Image for post

Shapes in Jetpack Compose

There are times when when we need to create views with custom forms. For example card with rounded corners, or message view in our chat application. Jetpack Compose provides various tools for creating a different type of shapes without any difficulties.

Canvas

Canvas is a component that allows us to draw various types of shapes any things we want. According to official documentation we must to specify our canvas size.

*You MUST specify size with modifier, whether with exact sizes via [Modifier.size]
* modifier, or relative to parent, via [Modifier.fillMaxSize], [ColumnScope.weight], etc. If parent
* wraps this child, only exact sizes must be specified.

It’s has similar functionality as the Canvas in Android. The only difference we can’t draw text in it.

For example, to draw circle in canvas we just use drawCircle method

@Composable
@Preview
fun CircleShape() {
Canvas(modifier = Modifier.size(50.dp), onDraw = {
val size = 50.dp.toPx()
drawCircle(
color = Color.Red,
radius = size / 2f
)
})
}

The output will be:

Image for post
Image for post

drawCircle is one of the methods that Jetpack Compose gives us. Let’s add a triangle to our canvas. For this purpose, we will use drawPath. First, create a triangle path:

val trianglePath = Path().apply {
// Moves to top center position
moveTo(size / 2f, 0f)
// Add line to bottom right corner
lineTo(size, size)
// Add line to bottom left corner
lineTo(0f, size)
}

Then just pass it to drawPath function

drawPath(
color = Color.Green,
path = trianglePath
)

Result will be like this:

Image for post
Image for post

As you can see every new shape draw over the previous result. To prevent the triangle overdraw the circle let’s make some changes to the code.

First of all set size to our Canvas.

modifier = Modifier.width(50.dp).height(100.dp)

Add following lines in onDraw method

val height = 100.dp.toPx()
val width = 50.dp.toPx()

Then set our circle’s center

drawCircle(
color = Color.Red,
radius = width / 2 ,
center = Offset(
x = width / 2,
y = height - circleRadius
)
)

Also we should change our triangle path

val trianglePath = Path().apply {
// Moves to top center position
moveTo(width / 2f, 0f)
// Add line to right corner above circle
lineTo(x = width, y = height - circleRadius * 2)
//Add line to left corner above circle
lineTo(x = 0f, y = height - circleRadius * 2)
}

And now our canvas looks like

Image for post
Image for post

Shapes

Well, the canvas is not the only way that we can create custom shapes.

For example, to create a rounded corner shape you can also use RoundedCornerShape. In next examples we will use Box component

Box is a base component in Jetpack Compose, that allows you to have items behind and/or in front of others.

Box(
modifier = Modifier
.size(50.dp)
.background(Color.Red, shape = RoundedCornerShape(25.dp))
) {

}

Or like this:

Box(
modifier = Modifier
.size(50.dp)
.clip(shape = RoundedCornerShape(25.dp))
.background(Color.Red)
) {

}

In both examples, the result will be a red circle.

But you should the difference between these two ways. If you will use a clip modifier it will clip your content to a selected shape. Otherwise setting shape in the background modifier, only changes the background to the selected shape.

Also, there is another way to create a shape. For this, we need to create a custom class that implements Shape interface.

And it has only one method which is called createOutline. Our shape creation will be there. Here is the example

class MyFirstShape : Shape {
override fun createOutline(size: Size, density: Density): Outline {
val trianglePath = Path().apply {
// Moves to top center position
moveTo(size.width / 2f, 0f)
// Add line to right corner above circle
lineTo(x = size.width, y = size.height)
//Add line to left corner above circle
lineTo(x = 0f, y = size.height)
}
return Outline.Generic(path = trianglePath)
}
}

As you can see there is no much difference with creating a path in Canvas. The only difference that we return a generic outline with our path.

And its usage is similar to the usage of other shapes.

Box(
modifier = Modifier
.size(50.dp)
.clip(shape = YourCustomShape())
.background(Color.Red)
)

More about shapes you can find there

https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/Shape.html

Conclusion

Shapes become needed when we create complex UI with custom components. Jetpack Compose offers us multiple choices to do it. Today we learned some of the ways of shape creation. Hope it will be helpful for you.

Feel free to follow me on Twitter, also don’t hesitate to ask questions related to this.

Thanks for reading, and see you later!

Android Software Engineer. Interested in mobile development. In love with Jetpack Compose

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store