Leave a rating/review
Notes: 14. Add Animations to Compose
The student materials have been reviewed and are updated as of September 2022.
Demo
Now that you’ve built most of the UI for the app, you’re really well versed with Jetpack Compose. If you open this episode’s starter project and run the app, you’ll see that the other features of the app have been prebuilt for you.
[Show the app, features]
Now you can focus fully on implementing the last UI feature your app needs - animations! :]
Animations are a great way to style your app, and bring meaningful motion to it. You’ll see how easy it is to animate values in Compose, so let’s jump straight into it.
There are two ways of animating components in Compose. The first is by directly animating properties, using the animateValueAsState() function, and the second is using complex transitions.
Let’s start off with the former. Open the ReadingListFrament.kt file, and add the following code:
val isShowingAddReadingList = _isShowingAddReadingListState.value
val size by animateDpAsState(if (isShowingAddReadingList) 0.dp else 56.dp)
FloatingActionButton(modifier = Modifier.size(size), onClick = {}
Using animateDpAsState() , you can define to which value you want to animate a certain property. Here you animate the size of the FloatingActionButton to be either 0dp if you’re currently showing the add reading list dialog, and to 56dp, which is the default size, if there is no dialog shown.
What’s going to happen here is, once you open the add reading list dialog, the button will shrink and go away, and once you close the dialog, it will expand and come in again.
Build & run the project, and play around with your animation! :]
[Build & Run]
That looks awesome! With just two lines of code, you added a small and cute animation to your app. With minimal effort you can make cool things happen using Compose!
There are many functions of a similar name - animateFloatAsState(), animateColorAsState() and many more. You use these for specific values and types of values you need to animate. Make sure to play around with different functions in your own projects!
Now let’s try to build a more complex transition. You’ll be animating content entry when the user opens the BookReviewDetails screen. Create a new File within the animation package, within the bookReviewDetails package, named BookReviewDetailsScreenState:
sealed class BookReviewDetailsScreenState
object Initial : BookReviewDetailsScreenState()
object Loaded : BookReviewDetailsScreenState()
This code represents the two states for your details screen. You only have two now - the Initial state for when the transition hasn’t started yet, and the Loaded state, when the transition is done.
Now to continue working on the transition, you need to define a transition building composable function. Create a new file called BookReviewDetailsTransition.kt and add the following code:
@Composable
fun animateBookReviewDetails(screenState: BookReviewDetailsScreenState): BookReviewDetailsTransitionState {
val transition = updateTransition(screenState, label = "bookReviewDetailsEntry")
}
This function will receive the state of the screen and will build a transition based on that state. Notice the return type, the BookReviewDetailsTransitionState.
This will represent the state of the transition, the actual values. Build the class on the bottom of the file, like so:
class BookReviewDetailsTransitionState {
var imageMarginTop: Dp by mutableStateOf(0.dp)
var floatingButtonSize: Dp by mutableStateOf(0.dp)
var titleMarginTop: Dp by mutableStateOf(0.dp)
var contentMarginTop: Dp by mutableStateOf(0.dp)
var contentAlpha: Float by mutableStateOf(0f)
}
Notice the states and the properties that are delegated by the states. These represent specific values that you’ll apply to your UI.
Now add the following code to the transition function to build the imageMarginTop state change:
val imageMarginTop by transition.animateDp(
transitionSpec = { tween(durationMillis = 1000) }, label = "imageMargin"
) { target -> if (target == Loaded) 16.dp else 125.dp }
Using animateDp(), you specify the tween() transition with a default non-linear interpolator, and one second duration. You also supply the label attribute, to help when inspecting the animation.
Finally, the trailing lambda represents the value specification - if you’re in the loaded state, the margin should be 16.dp, and in the Initial state it should be 125.dp.
The framework will know how to animate the values and it will do so in a nice and optimized way!
Now let’s add the remaining property changes. You can copy and paste most of the code, just be sure to change the names and values.
val floatingButtonSize by transition.animateDp(
transitionSpec = { tween(durationMillis = 1000) }, label = "FABSize"
) { target -> if (target == Loaded) 56.dp else 0.dp }
val titleMarginTop by transition.animateDp(
transitionSpec = { tween(durationMillis = 1000) }, label = "titleMargin"
) { target -> if (target == Loaded) 16.dp else 75.dp }
val contentMarginTop by transition.animateDp(
transitionSpec = { tween(durationMillis = 1000) }, label = "contentMargin"
) { target -> if (target == Loaded) 6.dp else 50.dp }
val contentAlpha by transition.animateFloat(
transitionSpec = { tween(durationMillis = 1000) }, label = "contentAlpha"
) { target -> if (target == Loaded) 1f else 0.3f }
That’s it. You have different properties here - mostly the margins for items on the UI, but also the alpha value for the textual content.
Now add the following code, that’ll represent the state changes:
val state = remember { BookReviewDetailsTransitionState() }
state.apply {
this.imageMarginTop = imageMarginTop
this.floatingButtonSize = floatingButtonSize
this.titleMarginTop = titleMarginTop
this.contentMarginTop = contentMarginTop
this.contentAlpha = contentAlpha
}
return state
You create a new remember block, that’ll keep the state of the transition in the compose tree. Then as the animation runs and changes, you apply these changes to the state, which will cause the compose tree to recompose and apply the new values.
This is a smart and nice way of injecting animation state in the compose tree.
Because you’ve built the animation, you can now move onto adding it to the compose tree and applying the state changes. Open the BookReviewDetailsActivity.kt file and add the following code on the top:
private val _screenState = mutableStateOf<BookReviewDetailsScreenState>(Initial)
This will represent the screen state. Now apply the following changes to BookReviewDetailsContent():
val animationState by _screenState
val state = animateBookReviewDetails(screenState)
LaunchedEffect(Unit, block = { // here
_screenState.value = Loaded
})
Scaffold(topBar = { BookReviewDetailsTopBar() },
floatingActionButton = { AddReadingEntry(state) }) { // here
BookReviewDetailsInformation(state) // here
}
You first load up the state and build an animation. Then you pass the animation state to the FloatingActionButton and the BookReviewDetailsInformation.
You also create a LaunchedEffect() to run the first time the tree composes, that changes the screen state. This will trigger the animation when you open the details screen.
Find the FAB function and add the following change:
FloatingActionButton(
modifier = Modifier.size(state.floatingButtonSize))
Notice how easy it is to integrate the animation into compose elements? :]
The animation values will be changed automatically based on the screen state. Now add the remaining changes, in BookReviewDetailsInformation:
Spacer(modifier = Modifier.height(state.imageMarginTop))
Spacer(modifier = Modifier.height(state.titleMarginTop))
Spacer(modifier = Modifier.height(state.contentMarginTop))
Spacer(modifier = Modifier.height(state.contentMarginTop))
Spacer(modifier = Modifier.height(state.contentMarginTop))
modifier = Modifier.alpha(state.contentAlpha)
Spacer(modifier = Modifier.height(state.contentMarginTop))
.alpha(state.contentAlpha)
Nicely done!
With each frame, at least 60 times per second, the values change slowly and by a little and they update the state. Once the state changes, the UI is updated and that happens until the animation finishes.
Now build & run the app to preview the animation.
[Build & Run, preview animation]
Pretty simple to build with Jetpack Compose, and yet so awesome to look at! You’re now ready to build a vast set of components from the framework, change their state, animate them, and much more.
In the next episode, you’ll start connecting compose to the MVVM architecture pattern. See you there! :]