Mohammed Ziyad
Mohammed Ziyad

Reputation: 61

How to hide bottom navigation bar while scrolling down and show it while scrolling up in jetpack compose?

I'm creating a simple app with bottom navigation bar. I want to hide bottom navigation bar while scrolling down and show it while scrolling up in a composable screen.

Any help would be appreciated. Please do let me know if you need any more code. I have attached all the code that I think are relevant to this problem.

This is my bottom navigation bar.

@Composable
fun BottomBar(navController: NavController) {

    val items = listOf(
        NavigationItem.Home,
        NavigationItem.Search
    )

    BottomNavigation(
        backgroundColor = MaterialTheme.colors.DarkRed,
        contentColor = Color.White
    ) {

        val navBackStackEntry by navController.currentBackStackEntryAsState()
        val currentRoute = navBackStackEntry?.destination?.route

        items.forEach { item ->
            BottomNavigationItem(
                selected = currentRoute == item.route,
                icon = {
                    Icon(
                        imageVector = item.icon,
                        contentDescription = "Icon",
                        modifier = Modifier.size(28.dp)

                    )
                },
                alwaysShowLabel = false,
                selectedContentColor = Color.White,
                unselectedContentColor = Color.White.copy(0.4f),
                onClick = {
                    navController.navigate(item.route){
                        // Pop up to the start destination of the graph to
                        // avoid building up a large stack of destinations
                        // on the back stack as users select items
                        navController.graph.startDestinationRoute?.let{route ->
                            popUpTo(route){
                                saveState = true
                            }
                        }
                        // Avoid multiple copies of the same destination when
                        // reselecting the same item
                        launchSingleTop = true
                        // Restore state when reselecting a previously selected item
                        restoreState = true
                    }

                }
            )
        }
    }
}

This is my Main Screen

@Composable
fun MainScreen(){

    val navController = rememberNavController()

    Scaffold(topBar = {
        ActionBar("Books")
    },
        bottomBar = {
            BottomBar(navController)
        }){

        NavigationGraph(navController = navController)
    }
}

And this NavigationGraph

@Composable
fun NavigationGraph(navController: NavHostController){

    NavHost(navController = navController, startDestination = NavigationItem.Home.route ){

        composable(NavigationItem.Home.route){
            HomeScreen()
        }
        composable(NavigationItem.Search.route){
            SearchScreen()
        }
    }
}

Upvotes: 5

Views: 2909

Answers (3)

Gastón Saillén
Gastón Saillén

Reputation: 13159

June 2024 Update

This is still experimental, but you can use the latest version of Material3 in which you have scrollBehavior inside BottomAppBar. Just use

 /**
 * <a href="https://m3.material.io/components/bottom-app-bar/overview" class="external" target="_blank">Material Design bottom app bar</a>.
 *
 * A bottom app bar displays navigation and key actions at the bottom of mobile screens.
 *
 * ![Bottom app bar image](https://developer.android.com/images/reference/androidx/compose/material3/bottom-app-bar.png)
 *
 * If you are interested in displaying a [FloatingActionButton], consider using another overload.
 *
 * Also see [NavigationBar].
 *
 * @param modifier the [Modifier] to be applied to this BottomAppBar
 * @param containerColor the color used for the background of this BottomAppBar. Use
 * [Color.Transparent] to have no color.
 * @param contentColor the preferred color for content inside this BottomAppBar. Defaults to either
 * the matching content color for [containerColor], or to the current [LocalContentColor] if
 * [containerColor] is not a color from the theme.
 * @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary color
 * overlay is applied on top of the container. A higher tonal elevation value will result in a
 * darker color in light theme and lighter color in dark theme. See also: [Surface].
 * @param contentPadding the padding applied to the content of this BottomAppBar
 * @param windowInsets a window insets that app bar will respect.
 * @param scrollBehavior a [BottomAppBarScrollBehavior] which holds various offset values that will
 * be applied by this bottom app bar to set up its height. A scroll behavior is designed to
 * work in conjunction with a scrolled content to change the bottom app bar appearance as the
 * content scrolls. See [BottomAppBarScrollBehavior.nestedScrollConnection].
 * @param content the content of this BottomAppBar. The default layout here is a [Row],
 * so content inside will be placed horizontally.
 */
@ExperimentalMaterial3Api
@Composable
fun BottomAppBar(
    modifier: Modifier = Modifier,
    containerColor: Color = BottomAppBarDefaults.containerColor,
    contentColor: Color = contentColorFor(containerColor),
    tonalElevation: Dp = BottomAppBarDefaults.ContainerElevation,
    contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
    windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets,
    scrollBehavior: BottomAppBarScrollBehavior? = null,
    content: @Composable RowScope.() -> Unit
) {

And then in your scaffold

 val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()

            CryptoTheme {
                Scaffold(
                    modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
                    bottomBar = {
                        BottomBarNavigation(
                            navController = navController,
                            scrollBehavior = scrollBehavior
                        )
                    },
                    containerColor = Color.Black
                ) { ...

With this, we don't need to do anything else.

My bottombar composable is

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomBarNavigation(modifier: Modifier = Modifier, navController: NavController,
                        scrollBehavior: BottomAppBarScrollBehavior? = null
) {

    ...

    BottomAppBar(
        modifier = modifier.fillMaxWidth(),
        containerColor = TransparentBackground,
        scrollBehavior = scrollBehavior
    ) { ... } }

Upvotes: 1

Marthia Ghasemi
Marthia Ghasemi

Reputation: 46

using Gautam Hazarika answer you can do this for better performance.

    val shouldHideBottomBar by remember(scrollState) {
     derivedStateOf { scrollState.firstVisibleItemIndex == 0 
    }  
    Scaffold ( 
              ... 
              bottomBar = { 
                           AnimatedVisibility(
                                visible = shouldHideBottomBar,
                                enter = slideInVertically(animationSpec = tween(durationMillis = 200)),
exit = slideOutVertically(animationSpec = tween(durationMillis = 200)), ) {
                            HomeBottomNavigation(bottomTab, setCurrentBottomTab)
                        }
                    }, 
    ) 
    { ... }

Upvotes: 0

Gautam Hazarika
Gautam Hazarika

Reputation: 181

You can use a LazyListState to track the state of the list and only show the BottomBar when the scrollState index is initial. This can be done by:

For LazyColumn:

@Composable
fun HomeScreen(scrollState: LazyListState) {

    LazyColumn(
        state = scrollState,
    ) {
        ...
      }
}

For NavigationGraph:

@Composable
fun NavigationGraph(navController: NavHostController, scrollState: LazyListState){

    NavHost(navController = navController, startDestination = NavigationItem.Home.route ){

        composable(NavigationItem.Home.route){
            HomeScreen(scrollState = scrollState)
        }
        composable(NavigationItem.Search.route){
            SearchScreen()
        }
    }
}

In the MainScreen:

@Composable
fun MainScreen(){

    val navController = rememberNavController()
    val scrollState = rememberLazyListState()

    Scaffold(topBar = {
        ActionBar("Books")
    },
        bottomBar = {
         if(scrollState.firstVisibleItemIndex == 0){ 
                  BottomBar(navController) 
           }
           
        }){

        NavigationGraph(navController = navController, scrollState = scrollState)
    }
}

This way the BottomBar will only show up when the list is at top.

Upvotes: 2

Related Questions