【问题标题】:hide Top and Bottom Navigator on a specific screen inside Scaffold Jetpack Compose在 Scaffold Jetpack Compose 内的特定屏幕上隐藏顶部和底部导航器
【发布时间】:2021-06-24 12:36:02
【问题描述】:

我正在创建一个带有底部导航和抽屉的简单应用。

我将所有屏幕都包裹在一个带有顶栏和底栏的 Scaffold 中。 我想隐藏特定屏幕上的顶栏和底栏。有谁知道如何实现这一目标

这里是设置导航的代码。

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 包含用于导航到屏幕的NavHost

【问题讨论】:

    标签: android kotlin android-jetpack android-jetpack-compose android-jetpack-navigation


    【解决方案1】:

    目前,我可以通过检查当前路线来显示或隐藏底部栏、顶部栏来实现这一点。但我认为必须有更好的解决方案。我将所有屏幕包裹在 Scaffold 中的方式可能不对。

    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】:

    我建议您将AnimatedVisibility 用于BottomNavigation 小部件和TopAppBar 小部件,我认为这是最清晰的撰写方式。

    1. 您应该使用remeberSaveable 来存储BottomBar 和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)) }
    
    1. 在composable函数中我们使用when来控制BottomBar和TopAppBar的状态,下面我们设置bottomBarState和topBarState为true,如果我们想显示BottomBar和TopAppBar,否则我们设置bottomBarState和topBarState 到false:
    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,
                    )
                }
            }
        }
    )
    

    重要提示:来自 Accompanist 的脚手架,在 build.gradle 中初始化。我们使用 Accompanist 的 Scaffold,因为我们需要完全控制填充,例如,在 Compose 的默认 Scaffold 中,如果我们有 TopAppBar,我们不能禁用顶部内容的填充。在我们的例子中,它是必需的,因为我们有 TopAppBar 的动画,内容应该在 TopAppBar 下,我们手动控制每个页面的填充。来自 Accompanist 的文档:https://google.github.io/accompanist/insets/.

    1. 将BottomNavigation 放入AnimatedVisibility 中,从bottomBarState 设置visible 值并设置enter 和exit 动画,在我的例子中,我使用slideInVertically 为enter 动画和@98765438 exit动画:
    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
                                }
                            }
                        )
                    }
                }
            }
        )
    
    1. 将TopAppBar 放入AnimatedVisibility 中,从topBarState 中设置visible 值并设置enter 和exit 动画,在我的例子中,我使用slideInVertically 代表enter 动画和@98765439 exit动画:
    AnimatedVisibility(
            visible = topBarState.value,
            enter = slideInVertically(initialOffsetY = { -it }),
            exit = slideOutVertically(targetOffsetY = { -it }),
            content = {
                TopAppBar(
                    title = { Text(text = title) },
                )
            }
        )
    

    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) },
                )
            }
        )
    }
    

    结果:

    不要忘记为撰写函数使用@ExperimentalAnimationApi 注解。

    更新: Compose 1.1.0 及以上版本@ExperimentalAnimationApi 不需要。

    22.02.2022 更新:我做了一些研究,更新点 2。现在我们使用when 来控制topBarState 和bottomBarState。

    完整代码可在 gitHub 上找到: https://github.com/AndreiRoze/BottomBarAnimation/tree/with_animated_topbar

    官方文档中提供的动画示例: https://developer.android.com/jetpack/compose/animation

    【讨论】:

    • 关注了这个答案,但我的底部栏移动滞后,我不知道为什么
    • @ysfcyln 没有代码很难回答,我在 2016 年生产的 OnePlus 3 上测试了该示例,没有滞后。
    • 我把它作为一个问题发布,你可以查看it。
    【解决方案3】:

    您可以使用compositionlocal,当您使用CompositionLocalProvider 包装您的mainActivity 时,将supportActionBar 传递给您的孩子可组合在您希望隐藏topBar 的屏幕上,调用.hide() 方法 在顶部。见下文:

    data class ShowAppBar(val show: ActionBar?)
    
    internal val LocalAppBar = compositionLocalOf<ShowAppBar>{ error("No ActionBar provided") }
    

    在mainActivity中,通过ActionBar传递

    val showy = ShowAppBar(show = supportActionBar )
    .....
    
     CompositionLocalProvider(
       LocalAppBar provides showy
      ) {
          YourTheme {
            yourApp()
          }
       }
    

    在屏幕上调用 >> LocalAppBar.current.show?.hide()

    【讨论】:

      猜你喜欢
      • 2021-06-08
      • 1970-01-01
      • 1970-01-01
      • 2019-11-06
      • 2021-12-24
      • 2023-03-03
      • 1970-01-01
      • 2020-08-31
      • 1970-01-01
      相关资源
      最近更新 更多