Using shapes in Jetpack Compose
Tags: android, kotlin, jetpackcompose
Published: 2020-12-27 14:40:33

In this article I will take a closer look at how to achieve custom drawing in Jetpack Compose using `androidx.compose.ui.graphics.Shape`. Let's start with something really simple:

![Three filled rectangles](https://dev-to-uploads.s3.amazonaws.com/i/ty0cy17hsgtphal65vz8.png)

```kotlin
@Composable
fun RowWithThreeRectangles() {
  Row(modifier = Modifier.fillMaxWidth()
  ) {
    Rectangle(color = Color.Red)
    Rectangle(modifier = Modifier.weight(0.3f)
        .height(64.dp),
        color = Color.Blue)
    Rectangle(modifier = Modifier.weight(0.7f),
        color = Color.Green)
  }
}
```

`Rectangle` receives a color and an optional modifier (which I use to control the size of the rectangle).

```kotlin
@Composable
fun Rectangle(color: Color,
       modifier: Modifier = Modifier) {
  Box(
      modifier = modifier.composed {
        preferredSize(32.dp)
            .clip(RectangleShape)
            .background(color)
      }
  )
}
```

It is just a `Box` which is clipped to a shape. `clip()` gets an instance of `androidx.compose.ui.graphics.Shape`. This interface has just one funtion, `createOutline()`, which receives two parameters:

- the size of the shape boundary
- the current density of the screen

It returns an instance of `androidx.compose.ui.graphics.Outline`. This class defines a (more or less) simple shape, which is used for bounding graphical regions by

- defining a shape of the component background or a shape of shadows cast by the component
- clipping the contents

I will cover `Outline` later. For now I stick to existing implementations of `Shape`.

```kotlin
@Composable
fun RowWithThreeCircles() {
  Row(modifier = Modifier.fillMaxWidth()
  ) {
    Circle(color = Color.Red)
    Circle(color = Color.Blue)
    Circle(color = Color.Green)
  }
}
```

![Three filled circles](https://dev-to-uploads.s3.amazonaws.com/i/bbtds2gr0qdu3eeehtwp.png)

```kotlin
@Composable
fun Circle(color: Color,
      modifier: Modifier = Modifier) {
  Box(
      modifier = modifier.composed {
        preferredSize(32.dp)
            .clip(CircleShape)
            .background(color)
      }
  )
}
```

The code is practically the same, only difference being that `CircleShape` is used. In your app you would probably refactor this into one function and just pass the `Shape`. `CircleShape`, by the way, is *not* a class but a variable. Its type is `RoundedCornerShape`, which extends abstract `CornerBasedShape`. Another child of this class is `CutCornerShape`. It produces a rectangle with cut corners. If you edit my `Rectangle` like this

```kotlin
.clip(CutCornerShape(8.dp))
```

it produces this output:

![Three rectangles with cut corners](https://dev-to-uploads.s3.amazonaws.com/i/bczroqenq8u0uncaadhg.png)

But what about shapes that are not covered by builtin code? In such cases you can use `GenericShape`. Take a look:

![A yellow triangle](https://dev-to-uploads.s3.amazonaws.com/i/913utazl4kh1uj73t9q1.png)

```kotlin
@Composable
fun RowWithATriangle() {
  Row() {
    Triangle(modifier = Modifier.fillMaxWidth(),
        color = Color.Yellow)
  }
}
```

I want the triangle to consume the full width, but leave its height at its preferred value.

```kotlin
@Composable
fun Triangle(color: Color,
       modifier: Modifier = Modifier) {
  Box(
      modifier = modifier.composed {
        preferredSize(64.dp)
            .clip(GenericShape { size ->
              moveTo(size.width / 2, 0f)
              lineTo(size.width - 1, size.height - 1)
              relativeLineTo(-size.width, 0f)
            })
            .background(color)
      }
  )
}
```

`size` is the size of the composable the shape is applied to. You can access its width with `size.width` and the height with `size.height`. The shape is defined through some basic instructions. Initially painting will start at the top left corner. `moveTo()` sets the current location of the pencil, whereas `lineTo()` draws a line from that positon to a new one. Did you notice that I don't move back to the starting point? As you shall see shortly this is done automatically by invoking a function named `close()`. You can check this if you add something like `relativeLineTo(32f, -32f)`. In this case we no longer have a triangle, but something that looks like this:

![Yellow shape with four corners](https://dev-to-uploads.s3.amazonaws.com/i/uqcwg7z5hf7kvqj0xy4f.png)

Where do these drawing primitives (and `close()`) come from? They are defined in the interface `androidx.compose.ui.graphics.Path`. To see how it is used by Compose itself let's peek into `GenericShape`.

![Source code of class GenericShape](https://dev-to-uploads.s3.amazonaws.com/i/8ck795wfp79jlduz4s3z.png)

So, `GenericShape` implements `createOutline()`, which is declared in `Shape`, by

- applying the provided drawing instructions to a `Path` instance
- calling `close()`
- returning an instance of `Outline.Generic`

Let's see where the implementation of `Path` comes from.

![Actual implementation of Path()](https://dev-to-uploads.s3.amazonaws.com/i/487b2u3upaz00jdjfiur.png)

Finally, what about `Outline`? Basically, it wraps a `Path`. It is not instantiated directly. Instead, you should use one of its nested classes `Rectangle`, `Rounded` or `Generic`. Let's see how this works. To get this

![A red donut](https://dev-to-uploads.s3.amazonaws.com/i/wo4eq92hl5iny5rxrffk.png)

I use two composables:

```kotlin
@Composable
fun RowWithADonut() {
  Row(
      modifier = Modifier.fillMaxWidth(),
      horizontalArrangement = Arrangement.Center) {
    Donut()
  }
}
```

```kotlin
@Composable
fun Donut() {
  Surface(modifier = Modifier.preferredSize(100.dp),
      color = Color.Red,
      shape = object : Shape {
        override fun createOutline(size: Size, density: Density): Outline {
          val thickness = size.height / 4
          val p1 = Path().apply {
            addOval(Rect(0f, 0f, size.width - 1, size.height - 1))
          }
          val p2 = Path().apply {
            addOval(Rect(thickness,
                thickness,
                size.width - 1 - thickness,
                size.height - 1 - thickness))
          }
          val p3 = Path()
          p3.op(p1, p2, PathOperation.difference)
          return Outline.Generic(p3)
        }
      }
  ) {
  }
}
```

Let's see what is worth mentioning here. First of all I use `Surface`, which gets the shape from `shape`. The color is set with, well, `color`. For the sake of simplicity I construct the shape in place. In your app you probably want to wrap it in a class, for example `DonutShape`. `createOutline()` does quite a few things. It creates three paths, one large and a smaller oval. The third path is the result of the difference of them. Think of it as cookie cutting. I remove the smaller one from the larger, which gives us a donut-like shape.

Have you worked with shapes and outlines, too? Please share your thoughts in the comments.

---
[source code](https://github.com/tkuenneth/android-demos/tree/master/compose/DrawingDemo)