Container Transform in Jetpack Compose
Add some delightful animations to your app with just a few lines of code
Introduction
In this article we will learn how to add container transform animations to our apps. The image below showcases what we will implement today:
Let's get started.
Dependencies
The APIs that we will be using today are part of the 1.7.0 alpha releases of Jetpack Compose, so we will need to update our dependencies file to select these alpha variants, as shown below
The composable elements
First we need to define the elements that compose this screen. We have three of them, the background list of users, the floating action button, and the input box to add new users. Let's look at each of them in turn.
The users list
The users list is not really relevant to the content transform animation, it's just there to add some purpose to the demo. This list is simply a LazyColum with user cards for each of the users, there isn't much to it, let's look at the code:
Let's have a look:
We define a data class for the users.
Next we have the composable for the list of users.
This composable is backed by LazyColum.
Each user is represented by a UserCard composable.
The UserCard composable is a Card wrapping 2 text items, one for the name and one for the email.
The text items, LineListItem, is simply a column with 2 text composables, one for the label and one for the content.
And that's it for the user list. This composable is very simple but that's all we need for our demo. Let's look next at the floating action button:
That's also pretty straightforward, we're simply leveraging the FloatingActionButton composable from the material library and adding an Add icon to it. We will flesh out the click handler later.
Next we have the input box, let's look at the code:
This composable is a bit larger, but there isn't much to it, let's have a look at the different elements:
The composable receives a lambda for when the user taps on the Add button, and another one for the Cancel button.
The root composable is a Card that we tint with one of the material colors from the theme.
For this demo, this composable manages its own state; usually you'd want this to be elsewhere, but we're keeping things simple for the demo.
We use a Column to stack the different sections vertically.
We use an OutlineTextField for the name input field.
And another one for the email field.
Finally we have a row of 2 buttons, for the Cancel and Add actions. Note that the add is only active if both input fields are populated.
And that's pretty much it for all the pieces of the demo app we need to showcase the container transform.
What we'll do next is put all the pieces together but without any animation, and then we'll add the container transform animation.
Adding it all together
First we will create the main composable that contains these 3 elements. For now we are just going to display all of them at once, without worrying about the logic to handle whether we are in the Fab mode or in the Add user mode. Let's see the code for our baseline:
If we run this, we get this result
Alright, we have all the pieces in place, let's add the animation.
To have access to the animation APIs we need to wrap the elements that will be animating in a SharedTransitionLayout - this composable exposes a scope, SharedTransitionScope, that will allow us to connect the 2 elements that we are using for the content transform animation. Within the SharedTransitionScope we have access to additional modifiers, similar to how within a Box we are in a BoxScope and can access Modifier.align. The Modifier that we want to use for the container transform is sharedBounds - when we want to animate between different kinds of content, as it's the case here, sharedBounds is the right choice. If, on the other hand we have a common element in the animation, like an image that changes size and position, then sharedElement would be the right choice.
The sharedBounds has 2 required arguments, the first is used to connect the elements that we are animating, and the second is an animated visibiliy scope that is the one responsible for the actual animation. For our animation, we will be using AnimatedContent - this composable provides the AnimatedVisibilityScope that we will pass to sharedBounds. Within the AnimatedContent we will display either the Fab or the input box, and toggling what is being displayed will trigger the animation. Let's have a look at the code, it will become much clearer when we do so:
We define a boolean to determine if we are in the Fab mode or the Adding user mode.
Next we wrap the content of our composable in a SharedTransitionLayout.
We add the AnimatedContent as a root of the content that is animating, the Fab and the user input box.
With an if statement on the boolean flag we defined earlier we determine whether we are in the adding user mode to display the add user box.
If we are in the adding use mode, then we show the InputBox composable.
If the user taps the Cancel button, we return to the Fab mode by toggling the flag.
And this is where the magic happens, we add the sharedBounds modifier, with a key and the AnimatedVisibilityScope that we get from the AnimatedContent parent.
In the else branch we show the Fab.
Like we did with the input box, we add the sharedBounds modifier, using the same key so that this composable is linked to the input box.
And finally we flesh out the Fab button to toggle the flag and show the input box.
And that's it, with just a few lines of code we have added an engaging container transform to our app. In this demo we've used all the defaults for the animation, but both AnimatedContent and sharedBounds offer customization options that I encourage you to explore on your own time. The complete source code for this demo is available on this GitHub gist.