Reimagine animations system for a delightful development experience with Jetpack Compose

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[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]
Info
Channel: Android Developers
Views: 5,286
Rating: undefined out of 5
Keywords: Android Developer Summit, Android Dev Summit, Android Dev Summit 2021, Android developer, Android development, new in android, new in android development, type: Conference Talk (Full production), pr_pr: Android
Id: Z_T1bVjhMLk
Channel Id: undefined
Length: 17min 38sec (1058 seconds)
Published: Wed Oct 27 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.