Nothing is displayed on the screen when using AnimatedNavHost - android

I am using Accompanist animation library:
implementation "com.google.accompanist:accompanist-navigation-animation:0.24.0-alpha"
And I have the following AnimatedNavHost:
val navController = rememberAnimatedNavController()
AnimatedNavHost(
navController = navController,
startDestination = "auth",
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None }
) {
composable(
route = "auth"
) {
AuthScreen(
navController = navController
)
}
composable(
route = "profile"
) {
ProfileScreen(
navController = navController
)
}
}
My AuthScreen is as simple as:
Box(
modifier = Modifier.fillMaxSize().padding(bottom = 48.dp),
contentAlignment = Alignment.BottomCenter
) {
Button(
onClick = {
signIn()
}
) {
Text(
text = SIGN_IN,
fontSize = 18.sp
)
}
When I launch the app nothing is displayed on the screen? No crash. Can anyone help? Thanks
P.S. I'm also using:
implementation "androidx.hilt:hilt-navigation-compose:1.0.0-rc01"
I'm using the following imports:
import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition

As per the Accompanist Navigation Animation migration guide:
To migrate from using the Navigation Compose APIs do the following:
Replace rememberNavController() with rememberAnimatedNavController()
Replace NavHost with AnimatedNavHost
Replace import androidx.navigation.compose.navigation with import com.google.accompanist.navigation.animation.navigation
Replace import androidx.navigation.compose.composable with import com.google.accompanist.navigation.animation.composable
I suspect you haven't actually done the last one - you need to use the Accompanist version of the composable NavGraphBuilder extension if you want your destination to be picked up by AnimatedNavHost.

Related

Conditional navigation start destination in jetpack compose

I am using navigation-compose along with bottom bar in jetpack compose. I want to show different bottom bar for different type of user. For that, I need to set conditional startDestination in NavHost. How do i do that?
I've tried below but it change startDestination but does not reflect in UI.
val user by remember { mutableStateOf(viewModel.user) }.collectAsState()
var startDestination = Screens.UserType1.route
LaunchedEffect(user) {
startDestination = if (loggedInUser?.userType == UserType.Seller) Screens.SellerHome.route else Screens.BuyerHome.route
}
While below code throws java.util.NoSuchElementException: List contains no element matching the predicate.
when (user?.userType) {
UserType.Seller -> {
startDestination = Screens.SellerHome.route
}
UserType.Buyer -> {
startDestination = Screens.BuyerHome.route
}
else -> {}
}
My NavHost
NavHost(
modifier = modifier,
navController = navController,
startDestination = startDestination
) {
...
}
Change this
var startDestination = Screens.UserType1.route
To this
var startDestination by remember { mutableStateOf(Screens.UserType1.route) }

Should I use Scaffold in every screen ? what are best practices while using topBar, bottomBar, drawer, etc. in compose

I am writing an android application pure in compose and I am using scaffold in every screen to implement topBar, bottomBar, fab, etc.
My question is should I be using scaffold in every screen or just in MainActivity?
what are the best practices while using composables?
Can I use scaffold inside of scaffold ?
I have researched a lot but didn't find answer anywhere even jetpack compose sample apps do not provide anything about best practices to build an app in jetpack compose.
please can anyone help me.
My code looks like this
MainActivity
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PasswordManagerApp()
}
}
}
#Composable
fun PasswordManagerApp() {
val mainViewModel: MainViewModel = hiltViewModel()
val navController = rememberNavController()
val systemUiController = rememberSystemUiController()
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope()
Theme(
darkTheme = mainViewModel.storedAppTheme.value
) {
Scaffold(
scaffoldState = scaffoldState,
snackbarHost = { scaffoldState.snackbarHostState }
) {
Box(modifier = Modifier) {
AppNavGraph(
mainViewModel = mainViewModel,
navController = navController,
scaffoldState = scaffoldState
)
DefaultSnackbar(
snackbarHostState = scaffoldState.snackbarHostState,
onDismiss = { scaffoldState.snackbarHostState.currentSnackbarData?.dismiss() },
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
}
}
Screen 1:
#Composable
fun LoginsScreen(
...
) {
...
Scaffold(
topBar = {
HomeTopAppBar(
topAppBarTitle = LoginsScreen.AllLogins.label,
onMenuIconClick = {},
switchState = viewModel.switch.value,
onSwitchIconClick = { viewModel.setSwitch(it) },
onSettingsIconClick = {navigateToSettings()}
)
},
scaffoldState = scaffoldState,
snackbarHost = { scaffoldState.snackbarHostState },
floatingActionButton = {
MyFloatingBtn(
onClick = { navigateToNewItem() }
)
},
drawerContent = {
//MyDrawer()
},
bottomBar = {
MyBottomBar(
navController = navController,
currentRoute = currentRoute,
navigateToAllLogins = navigateToAllLogins,
navigateToAllCards = navigateToAllCards,
navigateToAllOthers = navigateToAllOthers,
)
},
floatingActionButtonPosition = FabPosition.End,
isFloatingActionButtonDocked = false,
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(bottom = 48.dp)
.verticalScroll(scrollState)
) {...}
}
}
Screen 2:
#Composable
fun CardsScreen(
...
) {
...
Scaffold(
topBar = {
HomeTopAppBar(
topAppBarTitle = CardsScreen.AllCards.label,
onMenuIconClick = {},
switchState = viewModel.switch.value,
onSwitchIconClick = { viewModel.setSwitch(it) },
onSettingsIconClick = {navigateToSettings()}
)
},
floatingActionButton = {
MyFloatingBtn(
onClick = { navigateToNewItem() })
},
drawerContent = {
//MyDrawer()
},
bottomBar = {
MyBottomBar(
navController = navController,
currentRoute = currentRoute,
navigateToAllLogins = navigateToAllLogins,
navigateToAllCards = navigateToAllCards,
navigateToAllOthers = navigateToAllOthers,
)
},
floatingActionButtonPosition = FabPosition.End,
isFloatingActionButtonDocked = false
) {
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(bottom = 48.dp)
.verticalScroll(scrollState)
) {...}
}
Like the others in comments, i didn't find any guidelines.
It looks like everything is possible :
Nested Scaffolds
One Scaffold per app
One Scaffold per page
At first sight, you should choose the solution that match your needs.
But...
Keep in mind that one Scaffold per app could be the "better" solution because :
It's the only way to keep Snackbars alive when navigating.
It's the easiest way to have advanced transition animation between screens. I mean controlling animation component by component the way you want during transition (top bar, bottom nav, floating action button, page content...). As well, default NavHost transition animations look better with this solution.
With that solution, you'll have to deal with having different top app bar, floating action button... per page. That's something you can solve by having a shared view state for these elements.
#Immutable
data class ScaffoldViewState(
#StringRes val topAppBarTitle: Int? = null,
#StringRes val fabText: Int? = null,
// TODO : ...etc (top app bar actions, nav icon...)
)
var scaffoldViewState by remember {
mutableStateOf(ScaffoldViewState())
}
Scaffold(
topBar = {
TopAppBar(
title = {
scaffoldViewState.topAppBarTitle?.let {
Text(text = stringResource(id = it))
}
}
)
},
floatingActionButton = {
// TODO : a bit like for topBar
}
) {
NavHost {
composable("a") {
scaffoldViewState = // TODO : choose the top app bar and fab appearance for this page
// TODO : your page here
}
composable("b") {
scaffoldViewState = // TODO : choose the top app bar and fab appearance for this page
// TODO : your page here
}
}
}
As it has been said in comments both approaches are valid. Sure, you can use Scaffold inside another Scaffold like in the sample Jetnews app by Google
If you want "nice" screen transitions while only content changes, then define the component in top level Scaffold (e.g. the Drawer menu is usually shared in the top level Scaffold). Toolbars / AppBars are easier to implement per screen (inner Scaffold), not only in Jetpack Compose, because usually they have different titles, action buttons, etc.
In a simple app without dedicated Toolbars only one Scaffold could be used.

Jetpack Compose TopAppBar flickers after setting BottomNavView with Navigation component

Working on a Jetpack Compose app with a BottomNavigationView and Navigation component, I did the usual setup for the bottom bar inside the activity:
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
navigationView.setupWithNavController(navController)
In each screen I am using a composable TopAppBar.
Unfortunately, after adding the setupWithNavController call, the TopAppBar started flickering in all the navigation bar destinations (but not in deeper destinations).
How can I fix the flickering?
I got the same issue. What helped, to my surprise, was lowering androidx.navigation:navigation-compose version to 2.4.0-alpha04.
I cannot answer you exactly how to fix the flickering, but I can tell you that the recommended way to work with a Navigation component, TopBars and BottomBars in Jetpack Compose, is to use the Scaffold component and the navigation-compose component.
Compose Navigation component dependency:
// Navigation
implementation "androidx.navigation:navigation-compose:1.0.0-alpha09"
It is still in alpha, but so far it has been working really well. Just try to keep posted about all updates.
Example usage:
#Composable
fun App() {
val navController = rememberNavController()
Scaffold(
topBar = {
TopAppBar {
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(text = "App Title")
}
}
},
bottomBar = {
BottomAppBar {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
TextButton(onClick = { navController.navigate("home") }) {
Text(text = "Home")
}
TextButton(onClick = { navController.navigate("favorites") }) {
Text(text = "Favorites")
}
TextButton(onClick = { navController.navigate("profile") }) {
Text(text = "Profile")
}
}
}
}
) {
NavHost(navController, startDestination = "home") {
composable("home") {
Home()
}
composable("favorites") {
Favorites()
}
composable("profile") {
Profile()
}
}
}
}

hide Top and Bottom Navigator on a specific screen inside Scaffold Jetpack Compose

I'm creating a simple app with bottom navigation and drawer.
I wrap all screens inside a Scaffold with topbar and bottom bar.
I want to hide top bar and bottom bar on a specific screen. Does anyone know to how achieve that
here is the code for setting up navigation.
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
Scaffold(
bottomBar = {
AppBottomBar(navController)
},
topBar = {
AppTopBar(scaffoldState)
},
drawerContent = {
DrawerContent(navController, scaffoldState)
},
scaffoldState = scaffoldState
) {
// ovoid bottom bar overlay content
Column(modifier = Modifier.padding(bottom = 58.dp)) {
AppNavigation(navController)
}
}
AppNavigation contains NavHost for navigating to screens
I recommend you use AnimatedVisibility for BottomNavigation widget and TopAppBar widget, im my opinion it's clearest way for compose.
You should use remeberSaveable to store state of BottomBar and TopAppBar:
// State of bottomBar, set state to false, if current page route is "car_details"
val bottomBarState = rememberSaveable { (mutableStateOf(true)) }
// State of topBar, set state to false, if current page route is "car_details"
val topBarState = rememberSaveable { (mutableStateOf(true)) }
In composable function we used when for control state of BottomBar and TopAppBar, below we set bottomBarState and topBarState to true, if we would like to show BottomBar and TopAppBar, otherwise we set bottomBarState and topBarState to false:
val navController = rememberNavController()
// Subscribe to navBackStackEntry, required to get current route
val navBackStackEntry by navController.currentBackStackEntryAsState()
// Control TopBar and BottomBar
when (navBackStackEntry?.destination?.route) {
"cars" -> {
// Show BottomBar and TopBar
bottomBarState.value = true
topBarState.value = true
}
"bikes" -> {
// Show BottomBar and TopBar
bottomBarState.value = true
topBarState.value = true
}
"settings" -> {
// Show BottomBar and TopBar
bottomBarState.value = true
topBarState.value = true
}
"car_details" -> {
// Hide BottomBar and TopBar
bottomBarState.value = false
topBarState.value = false
}
}
com.google.accompanist.insets.ui.Scaffold(
bottomBar = {
BottomBar(
navController = navController,
bottomBarState = bottomBarState
)
},
topBar = {
TopBar(
navController = navController,
topBarState = topBarState
)
},
content = {
NavHost(
navController = navController,
startDestination = NavigationItem.Cars.route,
) {
composable(NavigationItem.Cars.route) {
CarsScreen(
navController = navController,
)
}
composable(NavigationItem.Bikes.route) {
BikesScreen(
navController = navController
)
}
composable(NavigationItem.Settings.route) {
SettingsScreen(
navController = navController,
)
}
composable(NavigationItem.CarDetails.route) {
CarDetailsScreen(
navController = navController,
)
}
}
}
)
Important: Scaffold from Accompanist, initialized in build.gradle. We use Scaffold from Accompanist, because we need full control of paddings, for example in default Scaffold from Compose we can't disable padding for content from top if we have TopAppBar. In our case it's required because we have animation for TopAppBar, content should be under TopAppBar and we manually control padding for each pages. Documentation from Accompanist: https://google.github.io/accompanist/insets/.
Put BottomNavigation inside AnimatedVisibility, set visible value from bottomBarState and set enter and exit animation, in my case I use slideInVertically for enter animation and slideOutVertically for exit animation:
AnimatedVisibility(
visible = bottomBarState.value,
enter = slideInVertically(initialOffsetY = { it }),
exit = slideOutVertically(targetOffsetY = { it }),
content = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
BottomNavigationItem(
icon = {
Icon(
painter = painterResource(id = item.icon),
contentDescription = item.title
)
},
label = { Text(text = item.title) },
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
)
Put TopAppBar inside AnimatedVisibility, set visible value from topBarState and set enter and exit animation, in my case I use slideInVertically for enter animation and slideOutVertically for exit animation:
AnimatedVisibility(
visible = topBarState.value,
enter = slideInVertically(initialOffsetY = { -it }),
exit = slideOutVertically(targetOffsetY = { -it }),
content = {
TopAppBar(
title = { Text(text = title) },
)
}
)
Full code of MainActivity:
package codes.andreirozov.bottombaranimation
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.res.painterResource
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import codes.andreirozov.bottombaranimation.screens.BikesScreen
import codes.andreirozov.bottombaranimation.screens.CarDetailsScreen
import codes.andreirozov.bottombaranimation.screens.CarsScreen
import codes.andreirozov.bottombaranimation.screens.SettingsScreen
import codes.andreirozov.bottombaranimation.ui.theme.BottomBarAnimationTheme
#ExperimentalAnimationApi
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BottomBarAnimationApp()
}
}
}
#ExperimentalAnimationApi
#Composable
fun BottomBarAnimationApp() {
// State of bottomBar, set state to false, if current page route is "car_details"
val bottomBarState = rememberSaveable { (mutableStateOf(true)) }
// State of topBar, set state to false, if current page route is "car_details"
val topBarState = rememberSaveable { (mutableStateOf(true)) }
BottomBarAnimationTheme {
val navController = rememberNavController()
// Subscribe to navBackStackEntry, required to get current route
val navBackStackEntry by navController.currentBackStackEntryAsState()
// Control TopBar and BottomBar
when (navBackStackEntry?.destination?.route) {
"cars" -> {
// Show BottomBar and TopBar
bottomBarState.value = true
topBarState.value = true
}
"bikes" -> {
// Show BottomBar and TopBar
bottomBarState.value = true
topBarState.value = true
}
"settings" -> {
// Show BottomBar and TopBar
bottomBarState.value = true
topBarState.value = true
}
"car_details" -> {
// Hide BottomBar and TopBar
bottomBarState.value = false
topBarState.value = false
}
}
// IMPORTANT, Scaffold from Accompanist, initialized in build.gradle.
// We use Scaffold from Accompanist, because we need full control of paddings, for example
// in default Scaffold from Compose we can't disable padding for content from top if we
// have TopAppBar. In our case it's required because we have animation for TopAppBar,
// content should be under TopAppBar and we manually control padding for each pages.
com.google.accompanist.insets.ui.Scaffold(
bottomBar = {
BottomBar(
navController = navController,
bottomBarState = bottomBarState
)
},
topBar = {
TopBar(
navController = navController,
topBarState = topBarState
)
},
content = {
NavHost(
navController = navController,
startDestination = NavigationItem.Cars.route,
) {
composable(NavigationItem.Cars.route) {
// show BottomBar and TopBar
LaunchedEffect(Unit) {
bottomBarState.value = true
topBarState.value = true
}
CarsScreen(
navController = navController,
)
}
composable(NavigationItem.Bikes.route) {
// show BottomBar and TopBar
LaunchedEffect(Unit) {
bottomBarState.value = true
topBarState.value = true
}
BikesScreen(
navController = navController
)
}
composable(NavigationItem.Settings.route) {
// show BottomBar and TopBar
LaunchedEffect(Unit) {
bottomBarState.value = true
topBarState.value = true
}
SettingsScreen(
navController = navController,
)
}
composable(NavigationItem.CarDetails.route) {
// hide BottomBar and TopBar
LaunchedEffect(Unit) {
bottomBarState.value = false
topBarState.value = false
}
CarDetailsScreen(
navController = navController,
)
}
}
}
)
}
}
#ExperimentalAnimationApi
#Composable
fun BottomBar(navController: NavController, bottomBarState: MutableState<Boolean>) {
val items = listOf(
NavigationItem.Cars,
NavigationItem.Bikes,
NavigationItem.Settings
)
AnimatedVisibility(
visible = bottomBarState.value,
enter = slideInVertically(initialOffsetY = { it }),
exit = slideOutVertically(targetOffsetY = { it }),
content = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
BottomNavigationItem(
icon = {
Icon(
painter = painterResource(id = item.icon),
contentDescription = item.title
)
},
label = { Text(text = item.title) },
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
)
}
#ExperimentalAnimationApi
#Composable
fun TopBar(navController: NavController, topBarState: MutableState<Boolean>) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val title: String = when (navBackStackEntry?.destination?.route ?: "cars") {
"cars" -> "Cars"
"bikes" -> "Bikes"
"settings" -> "Settings"
"car_details" -> "Cars"
else -> "Cars"
}
AnimatedVisibility(
visible = topBarState.value,
enter = slideInVertically(initialOffsetY = { -it }),
exit = slideOutVertically(targetOffsetY = { -it }),
content = {
TopAppBar(
title = { Text(text = title) },
)
}
)
}
Result:
Don't forget to use #ExperimentalAnimationApi annotation for compose functions.
Update: with Compose version 1.1.0 and above #ExperimentalAnimationApi not required.
22.02.2022 Update: I made some research, and update point 2. Now we use when for control topBarState and bottomBarState.
Full code available on gitHub:
https://github.com/AndreiRoze/BottomBarAnimation/tree/with_animated_topbar
Examples of animations available in the official documentation:
https://developer.android.com/jetpack/compose/animation
for now, I can achieve that by checking current route to show or hide bottomBar, topBar. But I think there's must be better solutions. The way I wrap all screens inside Scaffold might not right.
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
Scaffold(
bottomBar = {
if (currentRoute(navController) != "Example Screen") {
AppBottomBar(navController)
}
},
topBar = {
AppTopBar(scaffoldState)
},
drawerContent = {
DrawerContent(navController, scaffoldState)
},
floatingActionButton = {
FloatingButton(navController)
},
scaffoldState = scaffoldState
) {
// ovoid bottom bar overlay content
Column(modifier = Modifier.padding(bottom = 58.dp)) {
AppNavigation(navController)
}
}
#Composable
public fun currentRoute(navController: NavHostController): String? {
val navBackStackEntry by navController.currentBackStackEntryAsState()
return navBackStackEntry?.arguments?.getString(KEY_ROUTE)
}
Easiest solution I found (without animation)
fun MainScreen(modifier: Modifier = Modifier) {
val navController = rememberNavController()
var showBottomBar by rememberSaveable { mutableStateOf(true) }
val navBackStackEntry by navController.currentBackStackEntryAsState()
showBottomBar = when (navBackStackEntry?.destination?.route) {
"RouteOfScreenA" -> false // on this screen bottom bar should be hidden
"RouteOfScreenB" -> false // here too
else -> true // in all other cases show bottom bar
}
Scaffold(
modifier = modifier,
bottomBar = { if (showBottomBar) MyBottomNavigation(navController = navController) }
) { innerPadding ->
MyNavHost(
navController = navController,
modifier = Modifier.padding(innerPadding)
)
}
}
This worked for me. You get the current route from the currentRoute function and do a check in your bottombar composable to either hide or show the BottomNavigationView.
#Composable
fun currentRoute(navController: NavHostController): String? {
val navBackStackEntry by navController.currentBackStackEntryAsState()
return navBackStackEntry?.destination?.route
}
#Composable
fun MainScreenView() {
val navController = rememberNavController()
Scaffold(bottomBar = {
if (currentRoute(navController) != BottomNavItem.Setup.screen_route)
BottomNavigation(navController = navController)
}
) {
NavigationGraph(navController = navController)
}
}
you can use compositionlocal, and as you wrap your mainActivity with the CompositionLocalProvider, pass the supportActionBar to your child composable At the screen where you wish to hide the topBar, invoke the .hide() method
at the top. See below:
data class ShowAppBar(val show: ActionBar?)
internal val LocalAppBar = compositionLocalOf<ShowAppBar>{ error("No ActionBar provided") }
In mainActivity, pass the ActionBar via
val showy = ShowAppBar(show = supportActionBar )
.....
CompositionLocalProvider(
LocalAppBar provides showy
) {
YourTheme {
yourApp()
}
}
Invoking at the screen >> LocalAppBar.current.show?.hide()

Animating between Composables in Navigation with Compose

I have started trying out Navigation for compose.
I created my 2 Composables and everything is working fine.
But what I'm missing is Animations (or Transitions) between the pages. I didn't find any resources pointing out how to do it in Compose.
I know all animations are based on states in Compose, but the only thing I know is the Navigation Back Stack.
You can use the composable I made to show enter animation (configure preferable effects in "enter" and "exit" parameters)
fun EnterAnimation(content: #Composable () -> Unit) {
AnimatedVisibility(
visible = true,
enter = slideInVertically(
initialOffsetY = { -40 }
) + expandVertically(
expandFrom = Alignment.Top
) + fadeIn(initialAlpha = 0.3f),
exit = slideOutVertically() + shrinkVertically() + fadeOut(),
content = content,
initiallyVisible = false
)
}
You can use it like this:
NavHost(
navController = navController,
startDestination = "dest1"
) {
composable("dest1") {
EnterAnimation {
FirstScreen(navController)
}
}
composable("dest2") {
EnterAnimation {
SecondScreen(navController)
}
}
}
Accompanist version 0.16.1 and above supports animation between destinations. Here is the article for more info.
implementation("com.google.accompanist:accompanist-navigation-animation:0.16.1")
import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.composable
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
val navController = rememberAnimatedNavController()
AnimatedNavHost(navController, startDestination = "first") {
composable(
route = "first",
enterTransition = { _, _ -> slideInHorizontally(animationSpec = tween(500)) },
exitTransition = { _, _ -> slideOutHorizontally(animationSpec = tween(500)) }
) {
FirstScreen()
}
composable(
route = "second",
enterTransition = { _, _ -> slideInHorizontally(initialOffsetX = { it / 2 }, animationSpec = tween(500)) },
exitTransition = { _, _ -> slideOutHorizontally(targetOffsetX = { it / 2 }, animationSpec = tween(500)) }
) {
SecondScreen()
}
}
Result:
In alpha-09 this is not supported. :(
Please, star this issue: https://issuetracker.google.com/issues/172112072
Due to yesterday's update (version 2.4.0-alpha05):
Navigation Compose’s NavHost now always uses Crossfades when navigating through destinations.
Lately Google Accompanist has added a library which provides Compose Animation support for Jetpack Navigation Compose.. Do check it out. 👍🏻
https://github.com/google/accompanist/tree/main/navigation-animation
You can use the library I did. It provides easy navigation and is written using AnimatedVisibility so you can use compose transitions to animate any screen state (enter, exit)
https://github.com/vldi01/AndroidComposeRouting

Categories

Resources