Hello Jetpack Compose developers,
Today we’ll be continuing our blog series on animations in Jetpack Compose! This content was inspired by Nicole Terc’s Composable Sheep talk from droidcon NYC.
More basic animations
We’ll be continuing to use our drawSunflower
method and Sketch
composable to build animations in this post, so if you haven’t already, check out last week’s blog post to see how we built those functions!
Last week, we took a look at implementing a basic size animation for our sunflower garden.
Today, we’ll learn how to do animations with two other properties: angle and color.
Rotation animation
To add some rotation to our garden, we’ll first have to revisit our flower drawing code. This time, our animation state will need to be float that varies between 0f
and 360f
, to represent the angle offset. To incorporate the angle into our drawing code, we only need to make one simple change:
fun DrawScope.drawSunflower(sizePct: Float = 1f, rotation: Float = 0f) { drawPetals(sizePct, rotation) drawCenter(sizePct) } // drawCenter remains the same fun DrawScope.drawPetals(sizePct: Float, rotation: Float) { val numPetals = 8 var angle = rotation 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 }
}
Instead of starting the angle
variable at 0f
when making calculations for where the petal ovals will be drawn, we simply start at the current angle offset in the rotation. The rotation
angle only needs to be passed into the drawPetal
function, so the drawCenter
function can remain the same as before.
To set up the animation behavior, let’s remind ourselves of what the Sketch
composable and animation spec methods infiniteRepeatable
and tween
look like:
@Composable fun Sketch( modifier: Modifier = Modifier, targetValue: Float, animationSpec: AnimationSpec<Float>, onDraw: DrawScope.(Float) -> Unit ) @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>
We already noted that our animation state should vary between 0f
and 360f
to cover every angle, so we’ll set the targetValue
to 360f
.
For the animation spec, I want my sunflowers to rotate at a constant speed infinitely. To make the rotation look seamless as it repeats, we’ll leave the repeatMode
and delayMillis
parameters at their default values. And to achieve a constant rotation speed, this time we’ll use a LinearEasing
so the angle changes at a constant rate.
Putting all that together, we can write the AnimatedRotationSunflower
composable:
@Composable fun AnimatedRotationSunflower() { Sketch( modifier = Modifier.size(100.dp), targetValue = 360f, animationSpec = infiniteRepeatable(tween(durationMillis = 2000, easing = LinearEasing)) ) { angle -> drawSunflower(rotation = angle) } }
Look at our garden now!
Color animation
As beautiful as the sunflowers are in yellow and brown, I think the garden could benefit from some additional colors! Let’s try to add some color-based animations.
Thanks to the composable sheep talk, we know that Color.hsv
method is a great way to animate color based on angle, since the hue
parameter must be in the range of 0f-360f
.
To update our drawing code to accept our animated color, we only have to pass in the color parameter and use it when drawing our petal ovals:
fun DrawScope.drawSunflower(sizePct: Float = 1f, rotation: Float = 0f, color: Color = Yellow) { drawPetals(sizePct, rotation, color) drawCenter(sizePct) } // drawCenter remains the same fun DrawScope.drawPetals(sizePct: Float, rotation: Float, color: Color) { val numPetals = 8 var angle = rotation val size = Size(width = 30f, height = 75f) * sizePct repeat(numPetals) { rotate(angle) { drawOval( color = color, topLeft = Offset(center.x - size.width / 2, center.y), size = size ) } angle += 360 / numPetals } }
Super simple! As for our animation, I want very similar behavior to our rotation animation from above – seamless infinite looping, constant rate of change – so our final composable will look like this:
@Composable fun AnimatedColorSunflower() { Sketch( modifier = Modifier.size(100.dp), targetValue = 360f, animationSpec = infiniteRepeatable(tween(durationMillis = 3000, easing = LinearEasing)) ) { hue -> drawSunflower(color = Color.hsv(hue = hue, saturation = 1f, value = 1f)) } }
As previously mentioned, the Color.hsv
method accepts a hue between 0f
and 360f
, so we set our targetValue
to 360f
and pass in the animated value to generate a new color.
Here’s the result:
Pretty cool, but I think we can do even better! It would be awesome to have each individual petal change color over time for a sort of rainbow spinner effect. To do this, we’ll mostly have to update the drawing code so that each petal is drawn with a slightly different color. We’ll keep all the animation spec details the same, but instead of passing in one color for all the petals, we’ll now just pass in the hue and do calculations while drawing:
@Composable fun AnimatedPetalColorSunflower() { Sketch( modifier = Modifier.size(100.dp), targetValue = 360f, animationSpec = infiniteRepeatable(tween(durationMillis = 3000, easing = LinearEasing)) ) { hue -> drawSunflower(hue = hue) } } fun DrawScope.drawSunflower(sizePct: Float = 1f, rotation: Float = 0f, color: Color = Yellow, hue: Float? = null) { drawPetals(sizePct, rotation, color, hue) drawCenter(sizePct) } // drawCenter remains the same fun DrawScope.drawPetals(sizePct: Float, rotation: Float, color: Color, hue: Float?) { val numPetals = 8 var angle = rotation val size = Size(width = 30f, height = 75f) * sizePct repeat(numPetals) { petal -> rotate(angle) { drawOval( color = hue?.let { Color.hsv(hue = (hue + petal * 10f).mod(360f), saturation = 1f, value = 1f) } ?: color, topLeft = Offset(center.x - size.width / 2, center.y), size = size ) } angle += 360 / numPetals } }
You can see that we now call Color.hsv
in drawPetals
, and we use the current petal number to offset the hue value. Because we are adding to the hue, we do have to call mod(360f)
to make sure the final hue value is within the required range.
And here’s the final result – look at how much cooler our garden is now:
And that’s it for the additional basic animations we’ll cover today. The possibilities really are endless – you could also animate the number of petals, the position of the flower, or any combination of the properties we already animated. Next week, we’ll finish up this blog series by finalizing our flower varieties and adding some green 🟩!
Resources and feedback
The content covered today is part of PR #2 in the animated-garden repo.
To learn more about Jetpack Compose animations and creative coding, check out these resources:
- nicole-terc/composable-sheep (github.com)
- drinkthestars/composable-sheep-sketches: Funky composable sheep 😎 🐏 (github.com)
- Composable Sheep – A Compose Animations Journey
- Composable Sheep – The Creative Coding Epilogue
- Graphics in Jetpack Compose
- Animations in Jetpack Compose
- Customize animations in Jetpack Compose
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.
0 comments