在 Scaffold Jetpack Compose 内的特定屏幕上隐藏顶部和底部导航器
Posted
技术标签:
【中文标题】在 Scaffold Jetpack Compose 内的特定屏幕上隐藏顶部和底部导航器【英文标题】:hide Top and Bottom Navigator on a specific screen inside 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
【问题讨论】:
【参考方案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
小部件,我认为这是最清晰的撰写方式。
-
您应该使用
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))
-
在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/.
-
将
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
)
)
-
将
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()
【讨论】:
以上是关于在 Scaffold Jetpack Compose 内的特定屏幕上隐藏顶部和底部导航器的主要内容,如果未能解决你的问题,请参考以下文章
在 Scaffold Jetpack Compose 内的特定屏幕上隐藏顶部和底部导航器