[MUSIC PLAYING]
YUICHI ARAKI: Hello.
Welcome to our session, Animation Reimagined.
I'm Yuichi from Android Developer Relations team.
In this session, we walk through several animation
APIs in Compose and discuss how to use them effectively.
As the title of this session suggests,
the animation API in Compose is a brand new API
that we reimagined from the ground up.
Many APIs are declarative.
You can write a concise definition of your animation
in a declarative manner.
It is also interruptible.
When an animation is interrupted by another animation,
the values from an ongoing animation
are carried over to the new animation.
They are easy to use.
They are configured with reasonable default behaviors
out of the box.
And they are all highly customizable.
And last but not least, Android Studio
offers powerful tools to help implement complex animations.
Let's start with an easy example.
Here we have a cat icon appearing and disappearing
when we click the button.
The visible variable is a Boolean state.
Its value is toggled every time the button is clicked.
Any changes to the visible states invoke recomposition.
And the cat icon either appears or disappears.
If we want to animate this change, all we have to do
is to replace the IF statement with this animated visibility
composable.
When the state value changes, the animated visibility
composable runs animation between the two states.
There's another API very similar to animated visibility.
It's called animated content.
While animated visibility works on Enter and Exit of its child,
animated content can animate transitions
between content changes.
Here's a basic usage of the animated content composable.
Every time we click the button, the count
increases with fade outs and fade in.
The input state for animated content can be of any type.
In this example, we have an integer state called count.
We increment it when the button is clicked.
Animated content runs an animation every time
the states changes.
We can use the lambda parameter to switch the content
based on the input state.
Both animated visibility and animated content
provide a reasonable default animation style.
But of course, we can customize them.
For animated visibility, we can customize its Enter and Exit
transitions.
For animated content, we can customize the combination
of Enter and Exit transitions by the transition spec parameter.
Here's a list of some Enter transitions and Exit
transitions.
fadeIn, fadeOut, slideIn, slideOut, and scaleIn,
scaleOut.
They all have obvious names.
There are more options, so please
refer to the doc for the complete list.
Animated visibility and animated content
cover lots of practical use cases.
But let's take a look at more general APIs.
The animate*AsState API is used for animating a single value.
You can make various data types into an animating value
by simply wrapping it with the corresponding animate*AsState
function.
In this example, animating a dp value.
So we use animateDpAsState.
As I mentioned earlier, the state-based APIs
are interruptible.
This means if the state changes during an ongoing animation,
the new animation starts from the current intermediate values
and velocity and carries on based on spring physics.
Animation behavior like this is represented as animation spec.
Spring is the default animation spec.
Compose provides other types of animation spec.
For example, tween is a duration-based animation spec
that defines the motion by the duration of the animation
from the start to the end.
There are several more animation specs,
so please check out the doc.
Here's how you can specify an animation spec with
animate*AsState.
In this example, we specify that the animation takes 3 seconds.
So what should we do if we want to animate multiple values
at the same time?
The update transition API lets you do that.
This is useful for a very complex animation.
So let's take a look at a simpler example.
Here, we animate two values--
the size and the color of the box.
BoxState in an enum class representing the animation
targets--
either small or large.
Then we create a states object for that.
Changing the value of these states will invoke animation.
We can then use Update Transition to create
our transition objects.
It is a good practice to put a label on some objects we
use in transition API.
This way, Android Studio can provide
better display of animations.
But we'll cover that later.
After that, we can use extension functions
like animate color and animate dp to create animation values.
The returned values from these functions are states,
so we can use them like any other states.
By combining techniques we have seen so far,
we can achieve a very complex animation like this.
This uses Update Transition to animate
multiple values such as the height and the position
of the sheet as well as the alpha of its content.
We also have animated visibility with customized Enter and Exit
transitions to nicely fade in and fade out the button.
Now that we know how to create complex animations,
let's see how Android Studio can help
us create great animations.
Android Studio offers the Animation Preview feature
to quickly verify animations.
It automatically detects uses of animations.
It can play animations inside Android Studio.
It can also graph animation values
and lets you quickly glance how the values animate over time.
This icon on a Compose preview indicates that there
are inspectable animations.
Click on the button to enable the animation inspection.
The tool currently supports animated visibility
and update transition.
But we plan to add support for animated content
and animate as states as well.
Here we use the animation inspection window
to playback, scrub through, and slow down
the animated visibility.
The tool can also graph the animation curves
so that you can compare them with your designer's motion
specs.
This is useful for making sure that the animation values are
choreographed properly.
With that, I hand it over to Doris
to talk about complex animation scenarios.
DORIS LIU: Hi.
My name is Doris Liu.
I'm a software engineer on Android UI Toolkit team.
Yuichi has demonstrated a variety
of state-based animation APIs.
They're really helpful for animating state changes
for the common use cases.
What about more complex scenarios
where you might need custom behaviors for the animation?
For example, in some cases, you might need more control
over the animations.
You might need to sequence animations
or to sequence sets of animations.
You might want custom behavior when
the animation gets interrupted.
As we learned, state-based animation APIs
maintain continuity for animation value and velocity
when interrupted.
But in some cases, you might prefer this continuity
to emphasize gesture or responsiveness.
For example, in the Double Tap to Like animation on the right,
a new double tap will snap any ongoing animation
to the beginning.
You might have an indeterminate animation where you
don't know what the target is.
Flinging is such an example.
The target of a fling is only derived
from the starting conditions and its decay function.
I'm going to share with you another set of animation APIs
that will give you the control you need to handle
these intricate use cases.
To coordinate complex animations we're
going to use a powerful Kotlin feature--
coroutines.
Let's look at some animations that use coroutines
to see how we can achieve animation choreography
for the complex scenarios.
Here we have a basic coroutine animation API--
animate.
It will create an animation going
from the starting conditions specified by the initial value
parameter and the optional initial velocity parameter
until it reaches the target value.
An optional animation spec can be used to define the motion.
By default, a spring will be used.
Lastly, we have a block parameter.
On each animation frame, the block
will be invoked with the latest animation value and velocity.
Notice the suspend modifier to this animate function.
It means this function can be used in a coroutine.
And it can suspend the coroutine until the animation finishes.
This is the key for sequencing animations.
Here's a diagram to show what calling a suspending function
looks like.
You'll notice once Animate function is invoked,
the color coding gets suspended until the animation ends.
After that, the coroutine will resume and execute
subsequent work.
This allows us to easily sequence operations and execute
work after the animation.
Traditionally, we would have to put this type of work
in an animation end listener.
Thanks to coroutine, an end listener won't be necessary.
On the bottom, we have the code that produces
this workflow in the diagram.
It's pretty straightforward.
We first need to create a coroutine scope
inside composition using rememberCoroutineScope.
Then we'll use the launch function
to create a new coroutine in that scope.
In the new coroutine we'll first invoke Animate.
Animate will only return when the animation finishes.
Therefore, any work that needs to be
done after the animation, such as updating stays or starting
another animation, can be put below animate.
If we need to cancel the animation,
we can simply cancel the coroutine
that does the animation.
If we replace subsequent work with another animate function,
as you can see in the diagram, we now
have two animations running in sequence.
If you look at the code, it is simply
two animate functions called back to back
to achieve sequential animations.
Now that we've seen how to build sequential animations,
what if we want to run the animations at the same time?
Well, we can put them in separate coroutines
to run them in parallel.
To achieve that, we need a coroutine scope.
A coroutine scope defines a lifecycle for new coroutines
created in that scope.
Inside the scope, we can use coroutine builder function
launch to create new coroutines.
Launch is non-blocking.
This allows us to create multiple coroutines
in parallel in which we can run the animations simultaneously.
This is the same code for sequential animations
we showed before, except the highlighted launch functions.
Each of them creates a new coroutine.
As we mentioned previously, launch is non-blocking.
As a result, the new coroutines will be created in parallel.
And the animations will start running in the same frame.
Now we have simultaneous animations.
To recap, coroutines make it super flexible to coordinate
animations.
We can simply execute two animate functions
in the same coroutine to create sequential animations.
We can also run the animations in different coroutines
so that they run simultaneously.
These are the building blocks to more complex animations.
In this example, we're creating a heart animation
for Double Tap to Like, as you can see on the right.
There are two phases to this animation.
First, we need to fade in and scales out
the heart as it enters.
Once the enter animation is finished,
we'll kick off the exit animations
to fade out while scaling up the heart further.
To achieve this, we can create two coroutine scopes--
one for the Enter animations and the other one for the Exit
animations.
A coroutine scope will wait until all
of the animations running in that scope
to finish before returning.
Therefore, the Enter and Exit animations
will be running sequentially.
In each coroutine scope, we'll use launch function
to create new coroutines for fade and scale animations
to run simultaneously.
To build this animation, we'll first create mutable states
for alpha and scale so that we can update them
with animations.
Then we'll create two coroutine scopes.
They'll be running Enter animations and Exit animations
sequentially.
Inside of each coroutine scope we'll
use launch function to create separate coroutines
for the fade and scale animations to run together.
During the animation, we'll be updating alpha or scale mutable
state using the lambda in the animate function.
Now that we've covered the coroutine animation basics,
I'd like to take you through a more complex use case.
Here we have a content loading animation.
There's a gradient bar scanning from top to bottom
repeatedly while waiting for the content to load.
Once the content is loaded, if we're still
in the middle of a skim pass, we'll
wait for that pass to finish.
Then we'll do a final pass to reveal the content again
from top to bottom.
First, we'll need to create an animatable object.
It will keep track of the value and velocity of the animation.
Therefore, when creating a new animation using the animatable
object, all we need to provide is the new target value.
The current value and velocity will be carried over by default
as the starting condition for the new animation.
We'll be using this API for animating
the fraction of the scan.
Then in the coroutine scope created by launched effect,
we'll be using two suspending functions on the animatable.
One is animateTo.
The other is snapTo.
AnimateTo will start an animation to the new target
value from the current value and velocity of the animatable.
Whereas snapTo cancels any ongoing animation
and updates the value of the animatable
without using any animation.
Since we want to animate the gradient bar from top to bottom
first, then snap back to the top,
we'll call animateTo first with the 1 being the target using
a 2,000-millisecond twin animation followed by snapTo
to snap the bar back to the top.
Both animateTo and snapTo are suspending functions.
Therefore, we're able to sequence them and repeat
the sequence in a while loop until the loading is finished.
Since we only check the loading state before each scan pass,
any change to the loading state will only
take effect after the current pass is finished.
By doing this, we have created a custom interruption handling
behavior that is different than what state-based animation
APIs would do.
Once the content finishes loading,
we'll exit the while loop, change the reveal state
before doing a final pass to animate
the green bar to the bottom.
Finally, we can stop drawing the opaque cover in this overlay
when the reveal state becomes true so
that the content underneath is revealed in the final scan.
Last but not least, I'd like to share
some of my favorite animations built by the community.
Here's just a glimpse of the creativity
we've seen from our developer community.
In our journey of reimagining animation APIs
and building them for Compose, we
have received so much feedback from our community.
It has enabled us to shape the animation APIs to be
both intuitive and versatile.
We really appreciate all the feedback.
Please keep it coming.
And we look forward to seeing what you build with Compose.
To learn more, we have animation tutorials at developer.Androi
d.com/Jetpack/Co mpose/animation.
To learn more about coroutines, please take a look
at developer.Androi d.com/kotlin/coroutines.
If you have any questions or feedback,
our team is on Slack at kotlinlang.Slack.com
in the Compose channel.
Thank you.
[MUSIC PLAYING]