[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]