Estoy creando una aplicación sencilla con navegación inferior y cajón.
Envuelvo todas las pantallas dentro de un Scaffold con barra superior e inferior. Quiero ocultar la barra superior y la barra inferior en una pantalla específica. ¿Alguien sabe cómo lograr eso?
aquí está el código para configurar la navegación.
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 contiene NavHost para navegar a las pantallas
Utilizo varios NavHostController dentro del proyecto. Tengo mainNavController para las pantallas principales, como HomeScreen, LoginScreen, etc. y en HomeScreen, tengo Scaffold y Navi.gationBar, administro NavigationBar y las páginas de inicio (Perfil, Búsqueda, etc.) usando otro homeNavController definido en HomeScreen. Puedes seguir ese camino. Sólo otro punto de vista.
-yeulucay4 de septiembre de 2022 a las 21:23
Te recomiendo que utilices AnimatedVisibility para el widget BottomNavigation y el widget TopAppBar; en mi opinión, es la forma más clara de redactar.
Debes usar remeberSaveable para almacenar el estado de BottomBar y 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)) }
En la función componible usamos when para controlar el estado de BottomBar y TopAppBar, a continuación configuramos bottomBarState y topBarState en verdadero, si queremos mostrar BottomBar y TopAppBar; de lo contrario, configuramos bottomBarState y topBarState en falso:
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,
)
}
}
}
)
Importante: Scaffold de Accompanist, inicializado en build.gradle. Usamos Scaffold de Accompanist porque necesitamos control total de los rellenos; por ejemplo, en Scaffold predeterminado de Compose no podemos desactivar el relleno para el contenido desde arriba si tenemos TopAppBar. En nuestro caso es necesario porque tenemos animación para TopAppBar, el contenido debe estar en TopAppBar y controlamos manualmente el relleno de cada página. Documentación de Accompanist: https://google.github.io/accompanist/insets/.
Coloque BottomNavigation dentro de AnimatedVisibility, establezca el valor visible desde bottomBarState y configure la animación de entrada y salida. En mi caso, uso slideInVertically para ingresar a la animación y slideOutVertically para salir de la animación: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
}
}
)
}
}
}
)
Coloque TopAppBar dentro de AnimatedVisibility, establezca el valor visible de topBarState y configure la animación de entrada y salida. En mi caso, uso slideInVertically para ingresar a la animación y slideOutVertically para salir de la animación:
AnimatedVisibility(
visible = topBarState.value,
enter = slideInVertically(initialOffsetY = { -it }),
exit = slideOutVertically(targetOffsetY = { -it }),
content = {
TopAppBar(
title = { Text(text = title) },
)
}
)
Código completo de 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) },
)
}
)
}
Resultado:
No olvides utilizar la anotación @ExperimentalAnimationApi para las funciones de redacción.
Actualización: con Compose versión 1.1.0 y superior, no se requiere @ExperimentalAnimationApi.
Actualización del 22.02.2022: investigué un poco y actualicé el punto 2. Ahora usamos when para controlar topBarState y bottomBarState.
Código completo disponible en gitHub: https://github.com/AndreiRoze/BottomBarAnimation/tree/with_animated_topbar
Ejemplos de animaciones disponibles en la documentación oficial: https://desarrollador.android.com/jetpack/compose/animation/composables-modifiers
10
Seguí esta respuesta pero mi barra inferior se mueve con retraso, no sé por qué
-ysfcyln17/02/2022 a las 16:43
@ysfcyln es difícil de responder sin código, probé ese ejemplo en OnePlus 3, que se produjo en 2016, y no tuvo demoras.
-Andrei R.17/02/2022 a las 17:59
Lo publiqué como una pregunta, ¿puedes comprobarlo?
-ysfcyln18 de febrero de 2022 a las 8:27
¿Qué pasa si usamos PaddingValues del contenido de Scaffold y no podemos simplemente ocultar la navegación inferior?
- MohammadBaqer28/03/2022 a las 10:01
1
Si te desplazas hasta el final, la columna tendrá un comportamiento extraño. Ajustará nuevamente la altura de la barra de navegación inferior
– Yehezkiel L24 de enero de 2023 a las 13:47
Por ahora, puedo lograrlo marcando la ruta actual para mostrar u ocultar la barra inferior y la barra superior. Pero creo que debe haber mejores soluciones. Es posible que la forma en que envuelvo todas las pantallas dentro de Scaffold no sea correcta.
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)
}
2
2
Esta es una buena solución, pero actualícela si encuentra una mejor. Estoy explorando el mismo problema.
- Naveed22 dic 2021 a las 22:18
use 56.dp para ser más preciso con el diseño del material 3
- Kartik Garasia20/07/2022 a las 22:43
La solución más fácil que encontré (sin animación)
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)
)
}
}
puedes usar composición local y, a medida que envuelves tu actividad principal con CompositionLocalProvider, pasa la barra de acción de soporte a tu elemento componible secundario. En la pantalla donde deseas ocultar la barra superior, invoca el método .hide() en la cima. Ver a continuación:
data class ShowAppBar(val show: ActionBar?)
internal val LocalAppBar = compositionLocalOf<ShowAppBar>{ error("No ActionBar provided") }
En mainActivity, pasa la ActionBar vía
val showy = ShowAppBar(show = supportActionBar )
.....
CompositionLocalProvider(
LocalAppBar provides showy
) {
YourTheme {
yourApp()
}
}
Invocando en la pantalla >> LocalAppBar.curalquilar.mostrar?.ocultar()
Compartir Seguir Respondido el10 de marzo de 2022 a las 18:24
FlorenteFlorente
21
1
1 insignia de bronce
1
No he probado esta respuesta, peroparece mucho más escalable que otras respuestas.
- mrzbn25 de julio de 2023 a las 8:02
Esto funcionó para mí. Obtienes la ruta actual de la función currentRoute y haces una verificación en la barra inferior que se puede componer para ocultar o mostrar 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)
}
}
Compartir
Seguir
respondido 17 de mayo de 2022 a las 14:02
Juan MaricoolJuan Maricool
21
1
1 insignia de bronce