Как скрыть нижнюю панель в Jetpack Compose при использовании анимации навигации аккомпаниаторов
Ситуация
Я пишу довольно простое приложение, используя
Kotlin
&
Android Jetpack Compose
у меня есть
scaffold
содержащий мой
navHost
и а. Я могу использовать это для перехода между тремя основными экранами. На одном из этих основных экранов есть подробный экран, на котором не должно отображаться
bottomBar
.
Мой код
Пока что это было проще простого:
// MainActivitys onCreate
setContent {
val navController = rememberAnimatedNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route?.substringBeforeLast("/")
BottomBarNavTestTheme {
Scaffold(
bottomBar = {
if (currentRoute?.substringBeforeLast("/") == Screen.Detail.route) {
MyBottomNavigation(
navController,
currentRoute,
listOf(Screen.Dashboard, Screen.Map, Screen.Events) // main screens
)
}
}
) { innerPadding ->
NavHost( // to be replaced by AnimatedNavHost
navController = navController,
startDestination = Screen.Dashboard.route,
modifier = Modifier.padding(innerPadding)
) {
composable(Screen.Dashboard.route) { DashboardScreen() }
composable(Screen.Map.route) { MapScreen { navController.navigate(Screen.Detail.route) } }
composable(Screen.Events.route) { EventsScreen() }
composable(Screen.Detail.route) { MapDetailScreen() }
}
}
}
}
Проблема :
Мне нужны переходы между моими экранами, поэтому я использую анимацию навигации для аккомпаниаторов : просто замените
NavHost
с участием
AnimatedNavHost
.
При переходе от
mainScreen
к
detailScreen
возникает странный эффект:
- нижняя планка скрывает
- размер главного экрана изменится: (см. выравниваемый текст внизу)
- происходит анимация на экране деталей.
Выглядит плохо, как я могу это исправить?
6 ответов
В моем проекте я решил эту проблему следующим образом:
Сначала вам нужно удалить «Внутреннее дополнение» из Scaffold:
BottomBarNavTestTheme { Scaffold( ... ) { _ -> // rename param to "_" AnimatedNavHost( navController = navController, startDestination = Screen.Dashboard.route, // modifier = Modifier.padding(innerPadding) delete this modifier = Modifier .systemBarsPadding() // add this if you app "edge-to-edge" .navigationBarsPadding(), // add this if you app "edge-to-edge" ) { composable(Screen.Dashboard.route) { DashboardScreen() } composable(Screen.Map.route) { MapScreen { navController.navigate(Screen.Detail.route) } } composable(Screen.Events.route) { EventsScreen() } composable(Screen.Detail.route) { MapDetailScreen() } } } }
Добавьте эту аннотацию, где вы используете Scaffold(например, в основной деятельности):
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") class YourActivity() : FragmentActivity()
Так как мы удалили innerPadding из Scaffold(что автоматически добавляло отступы для верхней и нижней панели (если они видны)), теперь на каждом экране нужно вручную добавлять необходимые отступы. Для удобства предлагаю сделать две функции расширения:
// default heigth for bottom bar in material 3 - 80.dp // default heigth for top bar in material 3 - 64.dp fun Modifier.paddingBotAndTopBar(): Modifier { return padding(top = 64.dp, bottom = 80.dp) } fun Modifier.paddingTopBar(): Modifier { return padding(top = 64.dp) }
Теперь для каждого экрана для родительского @Composable нужно указать необходимый отступ в зависимости от видимости нижней/верхней панели на экране:
Column( modifier = Modifier .fillMaxSize() .paddingBotAndTopBar() // if bottom bar must be visible on this screen .paddingTopBar() // if bottom bar must be NOT visible on this screen ) { // your screen compose content }
Теперь анимации работают нормально, как вы и хотели.
Лучшее решение, которое я нашел до сих пор, — это установить нижний отступ для каждого экрана отдельно.
Я не использовал отступы в содержимом скаффолда.
Я использовал @SuppressLint("UnusedMaterialScaffoldPaddingParameter"), чтобы удалить предупреждение о том, что не используется дополнение в скаффолде.
На всех экранах, где видна нижняя полоса, я использовал нижний отступ 56 dp, что соответствует высоте нижней полосы Jetpack Compose.
Столкнулся с аналогичной проблемой в моем коде, и вот как я ее решил;
enum class HomeNavType {
DASHBOARD, ACCOUNT
}
@Composable
fun HomeScreen(navController: NavController) {
val homeNavItemState = rememberSaveable { mutableStateOf(HomeNavType.DASHBOARD) }
Scaffold(
content = { HomeContent(homeNavType = homeNavItemState.value , navController)},
bottomBar = { HomeBottomNavigation(homeNavItemState) }
)
}
@Composable
fun HomeBottomNavigation(homeNavItemState: MutableState<HomeNavType>) {
BottomNavigation() {
BottomNavigationItem(
selected = homeNavItemState.value === HomeNavType.DASHBOARD,
onClick = {
homeNavItemState.value = HomeNavType.DASHBOARD
},
icon = {
Icon(
painter = painterResource(id = R.drawable.ic_dashboard),
contentDescription = "Dashboard"
)
},
label = { Text("Dashboard") },
)
BottomNavigationItem(
selected = homeNavItemState.value === HomeNavType.ACCOUNT,
onClick = {
homeNavItemState.value = HomeNavType.ACCOUNT
},
icon = {
Icon(
painter = painterResource(id = R.drawable.ic_person),
contentDescription = "Account"
)
},
label = { Text("Account") },
)
}
}
@Composable
fun HomeContent(homeNavType: HomeNavType, navController: NavController) {
Crossfade(targetState = homeNavType) { navType ->
when (navType) {
HomeNavType.DASHBOARD -> DashboardScreen(navController)
HomeNavType.ACCOUNT -> AccountScreen(navController)
}
}
}
убедитесь, что инициализированы navController и navHostEngine. Создайте запечатанный класс, который содержит объекты пользовательских интерфейсов, которые будут добавлены в нижнюю навигацию. В скаффолде добавьте нижнюю панель, выполните итерацию по нижнему элементу навигации и проверьте, имеет ли каждый элемент пункт назначения в качестве маршрута, если это правда добавить нижнюю навигацию с указанными необходимыми данными.
val navController = rememberNavController()
val navHostEngine = rememberNavHostEngine()
val bottomNavigationItems: List<BottomNavItem> = listOf(
BottomNavItem.MainScreen,
BottomNavItem.FavoriteScreen,
BottomNavItem.UserScreen
)
Scaffold(
bottomBar = {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val route = navBackStackEntry?.destination?.route
bottomNavigationItems.forEach { item ->
if (item.destination.route == route) {
BottomNavigation(
backgroundColor = Color.White,
contentColor = Color.Black
) {
bottomNavigationItems.forEach { item ->
BottomNavigationItem(
icon = {
Icon(
imageVector = item.icon,
contentDescription = null
)
},
label = {
Text(text = item.title)
},
alwaysShowLabel = false,
selectedContentColor = green,
unselectedContentColor = Color.LightGray,
selected = currentDestination?.route?.contains(item.destination.route) == true,
onClick = {
navController.navigate(item.destination.route) {
navController.graph.startDestinationRoute?.let { screenRoute ->
popUpTo(screenRoute) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
}
}
) {
paddingValues ->
Box(
modifier = Modifier.padding(paddingValues)
) {
DestinationsNavHost(
navGraph = NavGraphs.root,
navController = navController,
engine = navHostEngine
)
}
}
Итак, мы пошли с обходным путем:
- на высшем уровне
scaffold
теперь больше не содержит - каждый экран, которому он сейчас нужен, теперь имеет свой собственный.
Это работает нормально, только щелчок
bottomBar
не так гладко, как хотелось бы (мы меняем его в середине щелчка, так что этого следовало ожидать)
Это также устраняет проблему, когда на экране было прокручиваемое содержимое, которое немного сбивало с толку расстояние прокрутки из-за его изменения при скрытии нижней панели.
Мое временное решение — сохранить тот же фиксированный размер, добавив его вBox
вместоScaffold
(с BottomBar) в приложении верхнего уровня с возможностью компонования и добавлением дополнительного настраиваемого заполнения нижней панели для каждого целевого экрана верхнего уровня:
Box(modifier = Modifier.fillMaxSize()) {
AppNavGraph( // NavHost or AnimatedNavHost
startDestination = appStartScreen,
navController = appState.navController,
modifier = Modifier.fillMaxSize()
)
// so the bottom bar will overlap the NavHost instead of placing itself below NavHost as it happens with Scaffold
Box(modifier = Modifier.align(Alignment.BottomCenter)) {
NavigationBar(
...
)
}
}
Экран назначения верхнего уровня, на котором видна нижняя панель:
@Composable
fun DashboardScreen( // top level destination, the bottom bar is visible
...
) {
Scaffold { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
// Screen Content
// add additional custom bottom bar height at the bottom of the screen
Spacer(modifier = Modifier.height(BottomBarContainerHeight))
}
}
}
БоттомБарКонтейнерХигхт :
val BottomBarContainerHeight = 80.0.dp // Material3's NavigationBar's height
По крайней мере, теперь у вас не будет проблем с анимацией перехода и смещением прокрутки (во время навигации вверх), потому чтоNavHost
размер всегда один и тот же