Custom Canvas Animations in Jetpack Compose ✨

Using Animatable to achieve Custom Canvas Animations

Rebecca Franks
Android Developers
Published in
5 min readMay 17, 2022

--

Imagine the smoothest app experience you’ve seen… Everything moving along with silky smooth transitions and sensible reactions to your interactions…. Are you drooling yet? 🤤 Well you no longer need to dream, as adding animations with Compose will make this dream a reality. Don’t believe me? Read on.

Compose has a range of Animation APIs that cover many different use cases. The simplest animation API is AnimatedVisibility. We can use it to animate the visibility of any Composable by wrapping it in an AnimatedVisibility Composable and providing a boolean that toggles the visibility:

AnimatedVisibility in action

This is a quick way to get fluid user interactions in your Compose apps. But what if we wanted to do something more complex, like animating the rotation or color of our custom drawing?

Custom drawing animating rotation and color change over time

The Animation documentation covers the different APIs that will enable you to achieve these sorts of animations and we are going to dive into one of those options: Animatable.

But before we jump into how to achieve this with Compose, let’s remind ourselves how to achieve custom animations in the standard view system:

Reminiscing on the Standard View approach 😶‍🌫️

Without Compose, you’d need to use something like ValueAnimator or ObjectAnimator to animate properties of your custom view. I covered this example in a previous talk.

Below is a sample of how to animate two properties of a custom view that draws a rounded rect, namely a rotation angle and a color Int. We use the ValueAnimator.ofPropertyValuesHolder to animate multiple properties at the same time, calling invalidate() on the view. This triggers a redraw and consequently, the animation runs as the properties are updated.

Using Compose to Animate Custom Drawing 🎨

To implement this animation in Compose, we create two remembered Animatable states–one for the angle, and one for the color. The angle will animate from 0–360° and the color will animate from Color.Green to Color.Blue, we will then use these two values to draw our elements on the Canvas.

All the animation logic is in the same Composable as the drawing logic. Leveraging the same classes and functions that are used in Composables — such as coroutines and Compose’s State class to store the animation progress.

You’ll notice the color animatable looks a bit different: We’ve defined a TwoWayConverter to convert the color into an AnimationVector. The Color.VectorConverter transforms between a Color and a 4 part AnimationVector storing the red, green, blue and alpha components of the color. In the previous view sample, we needed to add the ArgbEvaluator to achieve the same effect.

To start the animations as soon as the Composable is added to the Composition, we use the LaunchedEffect class. This will only run again when the provided keys change. We then use the animateTo function on the previously defined Animatable, this is a suspend function that needs to be called from a coroutine context.

In order to run the animations in parallel, we call launch twice, calling each animateTo in a new coroutine. If we wanted to run the animations sequentially, we would change the above LaunchedEffect block to use the same coroutine:

Running the animations in the same coroutine will suspend the thread until angle.animateTo() has completed running, then color.animateTo() will run. For more detail around using coroutines for animations — check out this video.

The last thing that remains is to use these two properties to draw something! ✏️ The Canvas onDraw function uses the animatable values — angle.value and color.value to rotate and set the color of the rounded rectangle. Compose automatically redraws when these values change. We don’t need to call invalidate.

That’s it! Using Animatable, we can achieve custom animations in the same way as we were previously using ValueAnimator. We’ve achieved a more readable version and less lines of code using the Compose version than the ValueAnimator approach.

And if less code hasn’t convinced you yet… Using keyframes might be the real drawcard. Keyframes in Compose give you the ability to change key sections (frames) of your animations at certain time intervals.

For example, if we want to set specific colors to animate at certain points of our animation, we can do the following:

The above example creates a keyframes AnimationSpec object, and provides it with the different Colors that should be animated to at specific time intervals with a specified Easing Curve.

It is worth noting that Animatable is a low level animation API offered by Compose, many of the higher level convenience APIs are based on top of Animatable. For example, animateColorAsState() wraps the above logic for Color conversions using TwoWayConverter under the hood. animateFloatAsState() could also be used for animating the angle. The animate*AsState APIs allow you to animate independently based on a state change, whereas Animatable offers more flexibility and control of your animation: allowing coordinating animations, indeterminate animations triggered by gestures etc.

For a more real world example, have a look at the custom date selection pill animation in the Crane sample.

For more information on different kinds of Animation in Compose, check out the Animation docs, the Animation codelab, or the Motion Compose sample on Github for more examples.

Share your drool-worthy animations with me on Twitter @riggaroo.

Bye for now! 👋

--

--

Rebecca Franks
Android Developers

Android Developer Relations Engineer at Google. London.