Animating the visibility of a BottomBar leads to "jumping" ui in Jetpack Compose

3.2k views Asked by At

I'm writing an android-app using jetpack compose.

This app has a bottom bar which i would like to hide sometimes using an animation. However, this proved challenging: as soon as i was dealing with a scrollable screen, there was some "jumping" of my ui - see end of post.

My minimal example looks like:

@Preview
@Composable
fun JumpingBottomBarScreen() {
    var bottomBarVisible by remember { mutableStateOf(false) }

    Scaffold(
        content = { padding ->
            Column(
                modifier = Modifier
                    .verticalScroll(rememberScrollState())
                    .fillMaxWidth()
                    .background(Color.LightGray)
                    .padding(padding)
            ) {
                (1..20).forEach { Text(text = "Test #$it of 50") }

                Button(
                    onClick = { bottomBarVisible = !bottomBarVisible },
                    content = { Text(if (bottomBarVisible) "Hide Bottom Bar" else "Show Bottom Bar") }
                )

                (21..50).forEach { Text(text = "Test #$it of 50") }
            }
        },
        bottomBar = {
            AnimatedVisibility(
                visible = bottomBarVisible,
                enter = slideInVertically(initialOffsetY = { it }),
                exit = slideOutVertically(targetOffsetY = { it })
            ) {
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(50.dp)
                        .background(Color.Red)
                )
            }
        }
    )
}

Avoiding AnimatedVisibility in favor of just offsetting works better, however, i only managed for fixed height bottom-bars, which makes this much less fail-safe.

bottomBar = {
    val bottomBarOffset by animateDpAsState(targetValue = if (bottomBarVisible) 0.dp else 50.dp)

    Box(
        modifier = Modifier
            .offset(y = bottomBarOffset)
            .fillMaxWidth()
            .height(50.dp)
            .background(Color.Red)
    )
}

How do i do this cleanly? I'm fine with my screen having more padding at the bottom than expected.


Bad on the left/top, good (but fixed height) on the right/bottom

bad

good

2

There are 2 answers

2
Krzysztof Dróbek On

You need to animate your Column padding as well:

@Composable
fun NotJumpingBottomBarScreen() {
    var bottomBarVisible by remember { mutableStateOf(false) }
    val bottomBarOffset by animateDpAsState(targetValue = if (bottomBarVisible) 0.dp else 50.dp)

    Scaffold(
        content = { padding ->
            Column(
                modifier = Modifier
                    .verticalScroll(rememberScrollState())
                    .fillMaxWidth()
                    .background(Color.LightGray)
                    .padding(
                        start = padding.calculateStartPadding(LocalLayoutDirection.current),
                        top = padding.calculateTopPadding(),
                        end = padding.calculateEndPadding(LocalLayoutDirection.current),
                        bottom = padding.calculateBottomPadding() - bottomBarOffset
                    )
            ) {
                (1..20).forEach { Text(text = "Test #$it of 50") }

                Button(
                    onClick = { bottomBarVisible = !bottomBarVisible },
                    content = { Text(if (bottomBarVisible) "Hide Bottom Bar" else "Show Bottom Bar") }
                )

                (21..50).forEach { Text(text = "Test #$it of 50") }
            }
        },
        bottomBar = {
            Box(
                modifier = Modifier
                    .offset(y = bottomBarOffset)
                    .fillMaxWidth()
                    .height(50.dp)
                    .offset(y = bottomBarOffset)
                    .background(Color.Red)
            )
        }
    )
}
0
Cameron On

If you remove the inner padding from the Scaffold content, and control each screen's padding individually, it solves this issue.

Scaffold(
    content = { padding ->
        Column(
            modifier = Modifier
                .verticalScroll(rememberScrollState())
                .fillMaxWidth()
                .background(Color.LightGray)
                .padding(padding) <--------------- Don't do this
        ) {
            // column content
        }
    },
    // other parameters
)