When using Jetpack Compose and Material Design, is it possible to use a new NavigationBar
(aka BottomNavigationBar
) on each screen? Or would this interrupt e.g. touch animations as a new NavigationBar
gets instantiated when switching screens?
My idea behind this is that this would really make it easy to hide or show the NavigationBar
on some screens and not on others (instead of communicating e.g. with a Scaffold
higher up in the hierarchy)
There is nothing special about BottomNavigation
composable as you can see in its source code below
@Composable
fun BottomNavigation(
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
elevation: Dp = BottomNavigationDefaults.Elevation,
content: @Composable RowScope.() -> Unit
) {
Surface(
color = backgroundColor,
contentColor = contentColor,
elevation = elevation,
modifier = modifier
) {
Row(
Modifier
.fillMaxWidth()
.height(BottomNavigationHeight)
.selectableGroup(),
horizontalArrangement = Arrangement.SpaceBetween,
content = content
)
}
}
calling BottomNavigation
on each screen is similar to using another Composable on any screen. Also, BottomNavigationItem
is just one Box
or two Box
es when both label and icon is present with progress for animating alpha and label offset.
@Composable
fun RowScope.BottomNavigationItem(
selected: Boolean,
onClick: () -> Unit,
icon: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
label: @Composable (() -> Unit)? = null,
alwaysShowLabel: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
selectedContentColor: Color = LocalContentColor.current,
unselectedContentColor: Color = selectedContentColor.copy(alpha = ContentAlpha.medium)
) {
val styledLabel: @Composable (() -> Unit)? = label?.let {
@Composable {
val style = MaterialTheme.typography.caption.copy(textAlign = TextAlign.Center)
ProvideTextStyle(style, content = label)
}
}
// The color of the Ripple should always the selected color, as we want to show the color
// before the item is considered selected, and hence before the new contentColor is
// provided by BottomNavigationTransition.
val ripple = rememberRipple(bounded = false, color = selectedContentColor)
Box(
modifier
.selectable(
selected = selected,
onClick = onClick,
enabled = enabled,
role = Role.Tab,
interactionSource = interactionSource,
indication = ripple
)
.weight(1f),
contentAlignment = Alignment.Center
) {
BottomNavigationTransition(
selectedContentColor,
unselectedContentColor,
selected
) { progress ->
val animationProgress = if (alwaysShowLabel) 1f else progress
BottomNavigationItemBaselineLayout(
icon = icon,
label = styledLabel,
iconPositionAnimationProgress = animationProgress
)
}
}
}
Or would this interrupt e.g. touch animations as a new NavigationBar gets instantiated when switching screens?
It depends on which kind of animation that is invoked since new BottomNavigation composable will be called on each screen when that screen enters composition.
If you have an animation that start when an item is clicked and takes a second to complete for instance, on new page you will likely to see target index as selected in target page while you might want this animation to continue as it was only on a single page.
BottomNavigationItem gets progress value from transition as
@Composable
private fun BottomNavigationTransition(
activeColor: Color,
inactiveColor: Color,
selected: Boolean,
content: @Composable (animationProgress: Float) -> Unit
) {
val animationProgress by animateFloatAsState(
targetValue = if (selected) 1f else 0f,
animationSpec = BottomNavigationAnimationSpec
)
val color = lerp(inactiveColor, activeColor, animationProgress)
CompositionLocalProvider(
LocalContentColor provides color.copy(alpha = 1f),
LocalContentAlpha provides color.alpha,
) {
content(animationProgress)
}
}
You can create you Composable like this to animate progress since writing you own BottomNavigationItem is straightforward and simple.
You can refer this answer about synching animations basically it's passing a progress value to multiple Composables if they don't enter composition at the same time.