Francisco Durdin Garcia
Francisco Durdin Garcia

Reputation: 13317

Collapse Navigation BottomBar while scrolling on Jetpack Compose

I'm looking forward to a way to implement a Collapsing effect while scrolling on LazyColumn list. I have been checking the docs but I didn't found nothing related. How can I implement it?

At the moment I'm using a BottomNavigation setted inside my Scaffold and I can add the inner paddings to the screen coming from the scaffold content lambda. But I didn't find any kind of scrollable state or something close to it.

Upvotes: 9

Views: 6300

Answers (3)

BenjyTec
BenjyTec

Reputation: 10333

There now is a dedicated BottomAppBarDefaults.exitAlwaysScrollBehavior() that you can use.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SimpleBottomAppBar() {

    val bottomAppBarScrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()

    Scaffold(
        modifier = Modifier.nestedScroll(bottomAppBarScrollBehavior.nestedScrollConnection),
        bottomBar = {
            BottomAppBar(
                windowInsets = WindowInsets.navigationBars,
                scrollBehavior = bottomAppBarScrollBehavior,
                actions = {
                    IconButton(onClick = { /* do something */ }) {
                        Icon(Icons.Filled.Check, contentDescription = "")
                    }
                }
            )
        }
    ) { paddingValues ->
        LazyColumn(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues)
        ) {
            items(50) { item ->
                Text("Item $item", modifier = Modifier.padding(16.dp))
            }
        }
    }
}

Output:

Screen Recording

Upvotes: 0

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

Reputation: 13129

This is the way I did it

val bottomBarHeight = remember { mutableStateOf(0f) }
            val bottomBarOffsetHeightPx = remember { mutableStateOf(0f) }
            val showBottomBar = remember { mutableStateOf(true) }

            val nestedScrollConnection = remember {
                object : NestedScrollConnection {
                    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                        val delta = available.y
                        val newOffset = bottomBarOffsetHeightPx.value + delta
                        bottomBarOffsetHeightPx.value = newOffset.coerceIn(-bottomBarHeight.value, 0f)
                        showBottomBar.value = newOffset >= 0f
                        return Offset.Zero
                    }
                }
            }

 Scaffold(
                    modifier = Modifier.nestedScroll(nestedScrollConnection),
                    contentWindowInsets = WindowInsets.safeDrawing,
                    topBar = { TopAppBarComposable(navController = navController) },
                    bottomBar = {
                        AnimatedVisibility(
                            visible = showBottomBar.value,
                            enter = slideInVertically(
                            initialOffsetY = { it },
                            animationSpec = tween(durationMillis = 150)
                        ),
                        exit = slideOutVertically(
                            targetOffsetY = { it },
                            animationSpec = tween(durationMillis = 150)
                        )
                        ) {
                            BottomBarNavigation(
                                modifier = Modifier
                        .onGloballyPositioned { coordinates ->
                            bottomBarHeight.value = coordinates.size.height.toFloat()
                        },
                                navController = navController
                            )
                        }
                    },
                    containerColor = Color.Black
                ) { ...

I also added onGloballyPositioned which will give us the real height of the bottomnav without hardcoding it.

This way we also hide the bottom nav, you will see a small bug in the background, that is what I was trying to fix but I did not find anything yet. I think it has to do with the recomposition and it's a little bit tricky to do it in real-time while the animation plays. If someone finds a solution, please add it to the answer.

Upvotes: 0

Gabriele Mariotti
Gabriele Mariotti

Reputation: 363439

You can use the nestedScroll modifier.

Something like:

val bottomBarHeight = 48.dp
val bottomBarHeightPx = with(LocalDensity.current) { bottomBarHeight.roundToPx().toFloat() }
val bottomBarOffsetHeightPx = remember { mutableStateOf(0f) }


// connection to the nested scroll system and listen to the scroll
// happening inside child LazyColumn
val nestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {

            val delta = available.y
            val newOffset = bottomBarOffsetHeightPx.value + delta
            bottomBarOffsetHeightPx.value = newOffset.coerceIn(-bottomBarHeightPx, 0f)

            return Offset.Zero
        }
    }
}

and then apply the nestedScroll to the Scaffold:

Scaffold(
    Modifier.nestedScroll(nestedScrollConnection),
    scaffoldState = scaffoldState,
    //..
    bottomBar = {
        BottomAppBar(modifier = Modifier
            .height(bottomBarHeight)
            .offset { IntOffset(x = 0, y = -bottomBarOffsetHeightPx.value.roundToInt()) }) {
            IconButton(
                onClick = {
                    coroutineScope.launch { scaffoldState.drawerState.open() }
                }
            ) {
                Icon(Icons.Filled.Menu, contentDescription = "Localized description")
            }
        }
    },

    content = { innerPadding ->
        LazyColumn(contentPadding = innerPadding) {
            items(count = 100) {
                Box(
                    Modifier
                        .fillMaxWidth()
                        .height(50.dp)
                        .background(colors[it % colors.size])
                )
            }
        }
    }
)

enter image description here

Upvotes: 16

Related Questions