DevBlogs
May 11th, 2023

# Budding love for compose animation

SW/FW Engineer

Hello Jetpack Compose developers,

Last week, we did some Compose animation work to build a typing indicator in a chat app. For our next blog series, I’ll be diving into more animation topics with a project inspired by Nicole Terc’s Composable Sheep talk from droidcon NYC. Her composable sheep talk series inspired me to work on my own colorful and playful animated garden project while learning Compose Multiplatform!

## Inspiration

For those of you who haven’t had a chance yet, I’d highly recommend watching the original Compose Sheep talk. There’s also a Composable Sheep sequel from droidcon London, where Nicole and Tasha partner up to show us some even cooler animation (and mathematical) concepts.

I used the sample repositories from these talks as reference for my own investigations:

Like the original sheep project, we’ll be using the Compose Graphics libraries (ex: `Canvas`, `DrawScope`…) and animation APIs to draw our projects frame by frame. All of the code covered today can be referenced in the Animated garden part 1 PR.

## Draw a flower

In order to start growing my garden, the first step was to figure out how to draw a flower. In `DrawScope`, you have access to many different functions for drawing text, shapes, and images.

To build a basic flower, we need two basic parts: the petals and the center of the flower. To draw these, we’ll use the `DrawScope` methods `drawOval` and `drawCircle`.

After some experimentation, I found the ideal sizes and colors for the first flower in my garden – a sunflower 🌻! The code to draw this flower is simple:

```  private val Brown = Color(0xFF742C0D)
private val Yellow = Color(0xFFF8D314)

@Composable
fun Sunflower() {
Canvas(Modifier.size(100.dp)) {
drawSunflower()
}
}

fun DrawScope.drawSunflower() {
drawPetals()
drawCenter()
}

fun DrawScope.drawCenter() {

}
fun DrawScope.drawPetals() {
val numPetals = 8
var angle = 0f
val size = Size(width = 30f, height = 75f)

repeat(numPetals) {        rotate(angle) {
drawOval(
color = Yellow,
topLeft = Offset(center.x - size.width / 2, center.y),
size = size
)
}        angle += 360 / numPetals
}
}```

Some subtle but important details from the code snippet:

• When using `Canvas`, you must provide a size modifier, otherwise it will default to zero and your drawings will not be shown
• Order matters in drawing operations – since we want the center shown above the petals, we have to make sure to draw it last
• In `DrawScope`, you gain access to fields like `center` and `size` that are really helpful for making calculations

No respectable garden has only one flower, so of course we have to add some code to grow the garden:

```  @Composable
fun MyGarden() {
LazyVerticalGrid(
modifier = Modifier.fillMaxSize(),
) {
items(8) {
Sunflower()
}
}
}
```

And with that, we have a nice basic garden full of sunflowers 😊

The next step to beautifying our garden is adding some animations. To set up our animations, the first thing we need to do is copy over a simplified version of the `Sketch` composable from the composable-sheep-sketches repo:

```  @Composable
fun Sketch(
modifier: Modifier = Modifier,
targetValue: Float,
animationSpec: AnimationSpec<Float>,
onDraw: DrawScope.(Float) -> Unit
) {
val animationState = remember { AnimationState(0f) }

LaunchedEffect(targetValue) {
while (isActive) {
animationState.animateTo(
targetValue = targetValue,
animationSpec = animationSpec,
sequentialAnimation = true
)
}
}
Canvas(modifier = modifier) {
onDraw(animationState.value)
}
}```

This will allow us to draw our animations frame by frame. Now, instead of wrapping our drawings with `Canvas`, we’ll use `Sketch`, and in the `onDraw` method you can see that you’ll have access to the `animationState` value. The `targetValue` and `animationSpec` parameters will allow you to customize the speed and style of animation, as you will see in later examples.

## Build a basic animation

Now that we have the animation canvas set up with the `Sketch` composable, we can start to build some fun animations for our garden.

In all basic animations, we’re essentially choosing one property to change over time. Once you’ve decided on a property, you can figure out what values you want to animate in between, how quickly the value should change, if the animation should repeat, etc.

For our first basic flower animation, we’ll be trying to animate the size of the flowers. To do that, we’ll have to:

• Update the flower drawing logic
• Connect the `drawSunflower` method to the animation canvas

### Update drawing logic

To change how big the flowers are drawn, we’ll have to change the code in two places:

• The `radius` parameter when we call `drawCircle` for the center of the flower
• The `size` parameter when we call `drawOval` for the petals of the flower

To ensure that the center and petals grow at the same rate, we can describe the size as a percentage instead of a specific value. This way, we won’t need to keep track of two different size values and we can easily create an animation state that varies between `0f` and `1f`.

I like the size of the basic sunflowers from the previous section, so let’s say we want to animate between zero up to the hardcoded sizes from earlier. To achieve that, the updated `drawSunflower` method would look like this:

```  fun DrawScope.drawSunflower(sizePct: Float) {
drawPetals(sizePct)
drawCenter(sizePct)
}

fun DrawScope.drawCenter(sizePct: Float) {
val radius = 20f * sizePct

}

fun DrawScope.drawPetals(sizePct: Float) {
val numPetals = 8
var angle = 0f
val size = Size(width = 30f, height = 75f) * sizePct

repeat(numPetals) {
rotate(angle) {
drawOval(
color = Yellow,
topLeft = Offset(center.x - size.width / 2, center.y),
size = size
)
}
angle += 360 / numPetals
}
}```

You can see the only differences are that we now have the `sizePct` float parameter, which will be our animation state that changes between `0f` and `1f`, and that we multiply the `radius` and `size` values by this percentage.

### Connect to animation canvas

The last step is to animate our sunflowers is to call `drawSunflower` inside our new `Sketch` animation canvas and pass along the animation state.

Let’s take a look at the `Sketch` composable’s parameters:

```  @Composable
fun Sketch(
modifier: Modifier = Modifier,
targetValue: Float,
animationSpec: AnimationSpec<Float>,
onDraw: DrawScope.(Float) -> Unit
)```

We have some typical composable parameters, including a modifier and a content parameter (`onDraw`) with the `DrawScope` receiver and `Float` parameter that will give us access to the animation state.

The `targetValue` parameter controls the end value of the animation state (the start value is always `0f` in our examples). For our `sizePct` animation state, we want the animation state to vary between `0f` and `1f`, so we’ll set `targetValue` to `1f`.

The `animationSpec` parameter lets you customize animation behavior, such as how quickly the animation state changes over time, if it repeats, etc. You can read more about the different options in the Customize animations documentation, but for our purposes, we’ll be using `infiniteRepeatable` to infinitely loop our animation and `tween` to describe our animation behavior.

Both functions accept three parameters:

```  @Stable
fun <T> infiniteRepeatable(
animation: DurationBasedAnimationSpec<T>,
repeatMode: RepeatMode = RepeatMode.Restart,
initialStartOffset: StartOffset = StartOffset(0)
): InfiniteRepeatableSpec<T>

@Stable
fun <T> tween(
durationMillis: Int = DefaultDurationMillis,
delayMillis: Int = 0,
easing: Easing = FastOutSlowInEasing
): TweenSpec<T>```

Let’s try calling these functions with their default behavior and see what the flower animation looks like:

```  @Composable
fun AnimatedSizeSunflower() {
Sketch(
modifier = Modifier.size(100.dp),
targetValue = 1f,
animationSpec = infiniteRepeatable(animation = tween())
) { sizePct ->        drawSunflower(sizePct = sizePct)
}
}```

Not bad! However, I have a few tweaks I want to make:

1. Slow down the animation

To do this, we’ll update the `tween` call with a longer `durationMillis` value.

2. Have the flower shrink as well as grow

We can update the `infiniteRepeatable` call by setting the `repeatMode` to `RepeatMode.Reverse`. That way, the flower will grow, then shrink, instead of restarting the growing animation each time.

3. Add a pause in between growing and shrinking

We can also address this by updating the `tween` call, this time by setting the `delayMillis` parameter to a non-zero value. This will add a delay before the animation starts.

4. Change the rate at which the flower grows and shrinks

For this, we’ll investigate different values for the `easing` parameter in the `tween` call. There are many easing options available for use, and you can experiment with them (and read up on Material Design) to find out which one works best for your use case.

After some experimentation, I settled on the following values for our animation spec:

```  @Composable
fun AnimatedSizeSunflower() {
Sketch(
modifier = Modifier.size(100.dp),
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 800, delayMillis = 50, easing = LinearOutSlowInEasing),
repeatMode = RepeatMode.Reverse        )
) { sizePct ->
drawSunflower(sizePct = sizePct)
}
}```

Take a look at the garden now:

So now, you know more about graphics in Compose, creating a frame-by-frame animation canvas, and building basic animations! Next week, we’ll dive into animating more flower properties (hint: 🌈) to make our garden even cooler 😎

## Resources and feedback

The content covered today is part of PR #1 in the animated-garden repo.

If you have any questions, use the feedback forum or message us on Twitter @surfaceduodev.

We won’t be livestreaming this week, but you can check out the archives on YouTube.

## Author

SW/FW Engineer

Works in the Surface Duo Developer Experience team to help with all aspects of dual-screen SDK development and customer engagement.