Jetpack Compose Animations Tutorial: Getting Started
In this tutorial, you’ll build beautiful animations with Jetpack Compose Animations, and discover the API that lets you build these animations easily. By Andres Torres.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Jetpack Compose Animations Tutorial: Getting Started
30 mins
- Getting Started
- Setting up Your Component
- Working With Jetpack Compose Animations
- Making the Button Change Width
- Making Your Component React to Value Changes
- Reversing the Animation
- Rounding the Corners of the Pressed State
- Changing Colors Between States
- Fading in/out Button Content
- Animating Idle State Using Keyframes
- Animating Pressed State Using Repeatable
- Where to Go From Here?
Making the Button Change Width
Now, you’ll learn how to define a transition animation for your component. In AnimatedFavButton.kt, replace the line with FavButton()
with the following code:
//1
val transitionDefinition = transitionDefinition<ButtonState> {
//2
state(ButtonState.IDLE) {
this[width] = 300.dp
}
//3
state(ButtonState.PRESSED) {
this[width] = 60.dp
}
//4
transition(fromState = ButtonState.IDLE, toState = ButtonState.PRESSED) {
width using tween(durationMillis = 1500)
}
}
//5
val state = transition(
definition = transitionDefinition,
initState = buttonState.value,
toState = ButtonState.PRESSED
)
FavButton(buttonState = buttonState, state = state)
Wow, that’s definitely a lot of code, but I promise it’ll be worth it! :]
Let’s dive bit by bit into this code:
- You create a variable for the
transitionDefinition
of your component. - Next, you declare an initial state where you tell the
transitionDefinition
that it will hold your previously defined widthDpPropKey
with a value of 300 dp for theIDLE
state. - Then you declare your final pressed state the same way you did for the initial state, but this time the value of the width
DpPropKey
will be 60 dp. - Following the declaration of the states, you declare the actual transition as an animation that goes from the values set in the initial state to the final state. You tell Jetpack Compose Animations the width
DpPropKey
will be animated usingtween()
and that the whole animation will take 1,500 milliseconds.tween()
component extendsDurationBasedAnimationBuilder
, which in turn extendsAnimationBuilder
and builds the tween animation from a start and end value, based on an easing curve and a duration. Other types ofAnimationBuilder
s you can work with to recreate different animations arePhysicsBuilder
,RepeatableBuilder
,SnapBuilder
andKeyframesBuilder
. You’ll work with some of them later in this tutorial. - Finally, you build the animation using
transition()
. Then you use the return value of the transition as aTransitionState
, and pass it, and thebuttonState
to theFavButton
.
You need to import the following packages to remove the compile errors:
import androidx.compose.animation.core.transitionDefinition
import androidx.compose.animation.core.tween
import androidx.compose.animation.transition
import androidx.compose.ui.unit.dp
You may notice a compile error on the signature of FavButton
. Don’t worry about it, you’ll fix it in a moment.
transition
within transitionDefinition
is very different from the second transition
component. The transition()
within transitionDefinition
defines the animation from one state to another, while the second transition()
takes the transitionDefinition
, target state, and builds a TransitionState
. You then use the state to read the value you animate, such as the button width in your case.
Making Your Component React to Value Changes
You almost have everything you need in terms of defining your animation. The last bit of code will go on the actual button so it uses the values that come from the state provided by the transition()
rather than fixed values. Open FavButton.kt and replace the code with the following:
@Composable
fun FavButton(buttonState: MutableState<ButtonState>, state: TransitionState) { //line changed
Button(
border = Border(1.dp, purple500),
backgroundColor = Color.White,
shape = RoundedCornerShape(6.dp),
modifier = Modifier.size(state[width], 60.dp), //line changed
onClick = {}
) {
ButtonContent()
}
}
You can fix the missing references using Alt+Enter in Android Studio, to import the types. You can see that changes occurred in just two lines.
The first change happened within the signature, where now you pass the state that will hold the values for each of your defined properties on each frame of the animation and the current button state, which you’ll use later in the tutorial, as parameters.
The second change happened on the size modifier, where instead of providing a fixed value for the width value, you are dynamically setting it up to be the value of the DpPropKey
in the state parameter.
Now, let’s look at what you have achieved. Build and run the project. Your button automatically transitions up from the idle to the pressed state, and in turn changes its width! :]
Congratulations! You’ve achieved quite a lot. Now that all the necessary components are in place, you can start having more fun with other types of animations. :]
Reversing the Animation
Wouldn’t it be cool for your button to animate itself or revert to the previous state based on a button click? With just a bit of tweaking and declaring a transition from the pressed to the idle state, you can achieve that in Jetpack Compose Animations. Open AnimatedFavButton.kt and define the following transition
within the composable function:
// 5
transition(ButtonState.PRESSED to ButtonState.IDLE) {
width using tween(durationMillis = 1500)
}
Like you did before, this describes the behavior and duration of the PropKey
that animate from a pressed to an idle state.
Now, replace everything underneath the transitionDefinition
with this:
// 1
val toState = if (buttonState.value == ButtonState.IDLE) {
ButtonState.PRESSED
} else {
ButtonState.IDLE
}
val state = transition(
definition = transitionDefinition,
initState = buttonState.value,
toState = toState // 2
)
FavButton(buttonState, state = state)
Here, you have two important changes. You first created a toState
value, to store the appropriate state, based on the initial buttonState
. Now, if the button is in IDLE
state, the toState
parameter will be PRESSED
and vice-versa. :]
And then you passed that value to the transition()
so that the animation supports both ways of animating. It's very easy to do in Jetpack Compose Animations.
Finally, you need to toggle the button state value when the user clicks the button! Open FavButton.kt and inside onClick()
, add the following code:
buttonState.value = if (buttonState.value == ButtonState.IDLE) {
ButtonState.PRESSED
} else {
ButtonState.IDLE
}
With this code, you're toggling the button state, when the user taps it.
Build and run. You can see the first animation from an idle to a pressed state go off. But now when you click the button, it actually goes to its previous state. Do it a couple of times and see how nice it looks!
Jetpack Compose Animations are just awesome, aren't they? :]
Rounding the Corners of the Pressed State
Now that you have the basic skeleton for going forward and backward with the button transitions built with Jetpack Compose Animations, you'll reinforce the previously acquired knowledge by changing the shape of the button with the pressed state. To do that, you'll animate the rounded corners property of the button. The rounded corners property of the button can be measured in multiple values.
You can measure it in dp, but this seems to be buggy, as not all corners receive the same radius. You can measure it in percentage, which seems to be working, to create a rounded button. And finally, you can measure it in a float amount of pixels. For your example, you'll use a percentage, as that's the most intuitive way of thinking.
Add a new IntPropKey
by opening the AnimPropKeys.kt file and adding the following code:
val roundedCorners = IntPropKey()
Then open AnimatedFavButton.kt and replace transitionDefinition
with the following:
val transitionDefinition = transitionDefinition<ButtonState> {
state(ButtonState.IDLE) {
this[width] = 300.dp
this[roundedCorners] = 6 // new code
}
state(ButtonState.PRESSED) {
this[width] = 60.dp
this[roundedCorners] = 50 // new code
}
transition(ButtonState.IDLE to ButtonState.PRESSED) {
width using tween(durationMillis = 1500)
// begin new code
roundedCorners using tween(
durationMillis = 3000,
easing = FastOutLinearInEasing
)
// end new code
}
transition(ButtonState.PRESSED to ButtonState.IDLE) {
width using tween(durationMillis = 1500)
// begin new code
roundedCorners using tween(
durationMillis = 3000,
easing = FastOutLinearInEasing
)
// end new code
}
}
Here, you've added idle and pressed state values for your roundedCorners
property and defined an animation builder and duration in each of the transitions. Make sure to import FastOutLinearInEasing
!
It's important to notice that these numbers go from 6 to 50, and are measured in percent (%) of the radius of your corners.
Finally, open FavButton.kt and replace the shape
parameter of the button, using this new line:
shape = RoundedCornerShape(state[roundedCorners]),
This dynamically changes the value of the rounded corners property on each frame. Again, make sure to import the roundedCorners
prop key.
One interesting property of TweenBuilder
is the ability to define a CubicBezierEasing
curve that modifies the behavior of the animation. For the roundedCorners
property, you add a FastOutLinearInEasing
curve, which animates the elements by starting at rest and ending at peak velocity. Other easing curves you can use are FastOutSlowInEasing
, LinearOutSlowInEasing
or LinearEasing
. Try them out and see what effects you can achieve in different properties of your Jetpack Compose Animations!
Now, build and run the app. Notice how the button has a nice rounded shape in the pressed state!