Android: oculta el navegador superior e inferior en una pantalla específica dentro de Scaffold Jetpack Compose

CorePress2024-01-25  10

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.

-yeulucay

4 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é

-ysfcyln

17/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?

-ysfcyln

18 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?

- MohammadBaqer

28/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 L

24 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.

- Naveed

22 dic 2021 a las 22:18

use 56.dp para ser más preciso con el diseño del material 3

- Kartik Garasia

20/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 el

10 de marzo de 2022 a las 18:24

Florente

Florente

21

1

1 insignia de bronce

1

No he probado esta respuesta, peroparece mucho más escalable que otras respuestas.

- mrzbn

25 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 Maricool

Juan Maricool

21

1

1 insignia de bronce

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare