Jetpack Compose and Screen Transition Animations
The accompanist compose animations got deprecated and migrated to the standard compose navigation library
Till recently, the navigation library did not provide support for the animations. If you wanted animations, you had to do them mostly independently or adopt an experimental accompanist library. It has changed with the update to 2.7.x and higher of the navigation library.
For those, who are here to migrate the animations from accompanist to the navigation library, please go below to the migration section.
Dependency
Firstly, add the following dependency to your build.gradle
:
implementation "androidx.navigation:navigation-compose:2.7.4"
The latest version can be found:
Creating a transition between 2 screens
If you have never used the compose navigation, you can read my tutorial about it:
For the base build, we have to have some foundation for the app, which is contained within the following MainCompose
.
fun MainCompose(
navController: NavHostController = rememberNavController(),
) {
AppAnimatedNavigationExample {
Surface {
NavHost(
navController
) {
exampleNavGraph(navController)
}
}
}
}
The main compose contains NavController
, which makes the app navigate through different composables. NavHost
nests all the routes, which your user can follow.
In the beginning, we will work only with one navigation graph with 2 screens. WelcomeScreen has a button to navigate to OtherScreen and OtherScreen has a back button to pop the navigation stack. Let's have a look at the implementation.
fun NavGraphBuilder.exampleGraph(navController: NavController) {
navigation(
startDestination = "WelcomeScreen", route = "ExampleRoute"
) {
composable("WelcomeScreen", enterTransition = {
return@composable fadeIn(tween(1000))
}, exitTransition = {
return@composable slideOutOfContainer(
AnimatedContentTransitionScope.SlideDirection.Start, tween(700)
)
}, popEnterTransition = {
return@composable slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.End, tween(700)
)
}) {
WelcomeScreen(navController)
}
composable(
"OtherScreen",
enterTransition = {
return@composable slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.Start, tween(700)
)
},
popExitTransition = {
return@composable slideOutOfContainer(
AnimatedContentTransitionScope.SlideDirection.End, tween(700)
)
},
) {
OtherScreen(navController)
}
}
}
Transitions
The navigation works as before with the compose navigation dependency. Nothing has changed. However, we now have 4 new fields at composable.
- entryTransition — called, if the navigation is about to show the composable
- exitTransition — called, if the navigation is removing the current composable
- popEnterTransition — called for the new composable, if the current one is about to be removed
- popExitTransition — called for the old composable, if the new one is about to be shown
For example, if the app is started, then the composable uses the entryTransition to show the WelcomeScreen composable.
If the user decides to move to OtherScreen, exitTransition is called for WelcomeScreen and is followed by another entryTransition for OtherScreen.
If the user decides to go back to WelcomeScreen, the popExitTransition is called for OtherScreen and is followed by another popEnterTransition for WelcomeScreen.
Animations
Firstly, it is important to point out that enter and exit animations use different types. The enter animations expect EnterTransition
types and exit animations expect ExitTransition
types. Usually, if you want to create animation, you want to animate composable into screen (EnterAnimation
). The previous composable should be animated out (ExitAnimation
) of the screen.
Examples of enter animations:
fadeIn(tween(700))
scaleIn(spring(Spring.DampingRatioHighBouncy))
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(700))
expandIn(tween(700, easing = LinearEasing))
Examples of exit animations:
fadeOut(tween(700))
scaleOut(spring(Spring.DampingRatioHighBouncy))
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(700))
shrinkOut(tween(700, easing = LinearEasing))
For more, I would like to encourage you to have a look at all animation parameters and experiment with different combinations of animation specifications and types. You are able to create your own animation too.
You might notice, that you do not see slideIn
/slideOut
in examples. This is because some of the animations require more specifications. Still, for the sake of simplicity, there are available alternatives as slideIntoContainer
, which makes transitions of whole composables without much detail needed.
Do not try to use
Left
orRight
equivalents of direction, but ratherStart
andEnd
because you will encounter problems with internationalization for regions, where they have flipped sides of reading.
Practical Example
For a complete example, the app will follow this user flow. The user will go through some introduction and afterwards, he will be moved to the main screen. During the process, the app will demonstrate multiple basic animations to transit through screens.
The most important are the composables within the navigation graph in MainCompose
. The navigation graph contains two separate graphs, one for the intro and the second one for the main screen.
@Composable
fun MainCompose(
navController: NavHostController = rememberNavController(),
) {
AppAnimatedNavigationExample {
Surface {
NavHost(
navController,
startDestination = NavRoutes.IntroRoute.name
) {
// navigation graphs
introGraph(navController)
mainGraph(navController)
}
}
}
}
Every graph now contains configured composable with transition animations.
Intro transitions
fun NavGraphBuilder.introGraph(navController: NavController) {
navigation(
startDestination = IntroNavOption.WelcomeScreen.name, route = NavRoutes.IntroRoute.name
) {
composable(IntroNavOption.WelcomeScreen.name, enterTransition = {
return@composable fadeIn(tween(1000))
}, exitTransition = {
return@composable fadeOut(tween(700))
}, popEnterTransition = {
return@composable slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.End, tween(700)
)
}) {
WelcomeScreen(navController)
}
composable(
IntroNavOption.MotivationScreen.name,
enterTransition = {
return@composable slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.Start, tween(700)
)
},
popExitTransition = {
return@composable slideOutOfContainer(
AnimatedContentTransitionScope.SlideDirection.End, tween(700)
)
},
) {
MotivationScreen(navController)
}
composable(IntroNavOption.RecommendationScreen.name, enterTransition = {
return@composable expandIn(tween(700))
}, exitTransition = {
return@composable shrinkOut(tween(700))
}, popExitTransition = { return@composable shrinkOut() }) {
RecommendationScreen(navController)
}
}
}
enum class IntroNavOption {
WelcomeScreen, MotivationScreen, RecommendationScreen
}
Main graph transitions
fun NavGraphBuilder.mainGraph(navController: NavHostController) {
navigation(startDestination = MainNavOption.HomeScreen.name, route = NavRoutes.MainRoute.name) {
composable(MainNavOption.HomeScreen.name, enterTransition = {
return@composable slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.Up,
tween(2000)
)
}) {
HomeScreen(navController)
}
}
}
enum class MainNavOption {
HomeScreen
}
Final result
The whole project can be found here:
Migration from Accompanist
1. Remove Accompanist dependency and replace/update your compose navigation dependency as it is on top of the page (version 2.7.0 and higher)
2. rememberAnimatedNavController
→ rememberNavController
3. AnimatedNavHost
→ NavHost
4. AnimatedComposeNavigator
→ ComposeNavigator
5. AnimatedComposeNavigator()
→ ComposeNavigator()
6. AnimatedComposeNavigator.Destination
→ ComposeNavigator.Destination
7. Remove all Accompanist imports from the project
Thanks for reading! If you like the content do not forget to follow me for more!
For more Android content: