Как реализовать шаблон BottomAppBar и BottomDrawer с помощью Android Jetpack Compose?
Я создаю приложение для Android с помощью Jetpack Compose. Застрял при попытке реализовать BottomAppBar с шаблоном BottomDrawer.
Нижние панели навигации - это модальные панели, которые прикреплены к нижней части экрана, а не к левому или правому краю. Они используются только с нижними панелями приложений. Эти ящики открываются при нажатии значка меню навигации на нижней панели приложения.
Описание на material.io и прямая ссылка на видео .
Я пробовал использовать Scaffold, но он поддерживает только боковой ящик. BottomDrawer, добавленный к содержимому Scaffold, отображается в области содержимого, а BottomDrawer не закрывает BottomAppBar при открытии. Перемещение BottomDrawer после функции Scaffold тоже не помогает: BottomAppBar покрыт каким-то невидимым блоком и не позволяет нажимать кнопки.
Я также пробовал использовать BottomSheetScaffold, но у него нет слота BottomAppBar.
Если Scaffold не поддерживает этот шаблон, как правильно его реализовать? Можно ли расширить компонент Scaffold? Боюсь, что неправильная реализация с нуля может создать проблемы позже, когда я попытаюсь реализовать навигацию и закусочную.
3 ответа
Они (разработчики Google) приглашают вас на путь создания макетов Jetpack, чтобы попробовать добавить другие компоненты материального дизайна, такие как
BottomNavigation
или в их соответствующие слоты , и все же не дают вам решения.
имеет собственный слот в (т. е.
bottomBar
), но нет - и, похоже, он разработан исключительно для использования с BottomAppBar явно (см. документацию API для BottomDrawer).
На этом этапе пути Jetpack Compose мы рассмотрели подъем состояния , слоты , модификаторы и были подробно объяснены, что время от времени нам придется экспериментировать, чтобы увидеть, как лучше всего складывать и организовывать Composables - что они почти всегда иметь естественно выразимый способ, которым они работают лучше всего, что практически задумано.
Позвольте мне настроить нас так, чтобы мы были на одной странице:
class MainActivity : ComponentActivity() {
@ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LayoutsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
LayoutsCodelab()
}
}
}
}
}
Это основное действие, вызывающее наш основной / основной Composable. Это точно так же, как в кодовой лаборатории, за исключением
@ExperimentalMaterialApi
аннотация.
Далее идет наш основной / основной Composable:
@ExperimentalMaterialApi
@Composable
fun LayoutsCodelab() {
val ( gesturesEnabled, toggleGesturesEnabled ) = remember { mutableStateOf( true ) }
val scope = rememberCoroutineScope()
val drawerState = rememberBottomDrawerState( BottomDrawerValue.Closed )
// BottomDrawer has to be the true core of our layout
BottomDrawer(
gesturesEnabled = gesturesEnabled,
drawerState = drawerState,
drawerContent = {
Button(
modifier = Modifier.align( Alignment.CenterHorizontally ).padding( top = 16.dp ),
onClick = { scope.launch { drawerState.close() } },
content = { Text( "Close Drawer" ) }
)
LazyColumn {
items( 25 ) {
ListItem(
text = { Text( "Item $it" ) },
icon = {
Icon(
Icons.Default.Favorite,
contentDescription = "Localized description"
)
}
)
}
}
},
// The API describes this member as "the content of the
// rest of the UI"
content = {
// So let's place the Scaffold here
Scaffold(
topBar = {
AppBarContent()
},
//drawerContent = { BottomBar() } // <-- Will implement a side drawer
bottomBar = {
BottomBarContent(
coroutineScope = scope,
drawerState = drawerState
)
},
) {
innerPadding ->
BodyContent( Modifier.padding( innerPadding ).fillMaxHeight() )
}
}
)
}
Здесь мы использовали точно так, как нам следует из codelab в пути compose. Обратите внимание на мой комментарий, который является автоматической реализацией бокового ящика. Это довольно изящный способ обойтись напрямую, используя [соответствующие] Composable (ы) (модальный ящик / лист материального дизайна)! Однако для нашего. Я думаю, что API является экспериментальным , потому что они будут вносить изменения, чтобы добавить его поддержку в Composables, как в будущем.
Я основываю это на том, насколько сложно использовать, предназначенный для использования исключительно с, с -, который явно содержит слот для.
Поддерживать
BottomDrawer
, мы должны понимать, что это базовый контроллер макета, который обертывает весь пользовательский интерфейс приложения, предотвращая взаимодействие с чем-либо, кроме его
drawerContent
когда ящик открыт. Для этого необходимо, чтобы он включал
Scaffold
, а это требует, чтобы мы делегировали необходимый государственный контроль -
BottomBarContent
составной, который обертывает наши
BottomAppBar
реализация:
@ExperimentalMaterialApi
@Composable
fun BottomBarContent( modifier: Modifier = Modifier, coroutineScope: CoroutineScope, drawerState: BottomDrawerState ) {
BottomAppBar{
// Leading icons should typically have a high content alpha
CompositionLocalProvider( LocalContentAlpha provides ContentAlpha.high ) {
IconButton(
onClick = {
coroutineScope.launch { drawerState.open() }
}
) {
Icon( Icons.Filled.Menu, contentDescription = "Localized description" )
}
}
// The actions should be at the end of the BottomAppBar. They use the default medium
// content alpha provided by BottomAppBar
Spacer( Modifier.weight( 1f, true ) )
IconButton( onClick = { /* doSomething() */ } ) {
Icon( Icons.Filled.Favorite, contentDescription = "Localized description" )
}
IconButton( onClick = { /* doSomething() */ } ) {
Icon( Icons.Filled.Favorite, contentDescription = "Localized description" )
}
}
}
Результат показывает нам:
- TopAppBar на вершине ,
- BottomAppBar в нижней части ,
- Щелчок по значку меню в BottomAppBar открывает наш BottomDrawer , соответствующим образом закрывая BottomAppBar и все пространство содержимого, когда оно открыто.
- BottomDrawer правильно скрыта, пока либо выше ссылки нажатие кнопки - или жест - не используется , чтобы открыть нижний ящик.
- Значок меню в BottomAppBar частично открывает ящик.
- Жест открывает нижний ящик наполовину быстрым коротким движением, но если вы направите его в противном случае.
Я думаю, что в последней версии scaffold есть параметр нижней панели приложения
Возможно, вам придется сделать что-то, как показано ниже. Обратите внимание, как Scaffold вызывается внутри BottomDrawer(). Это сбивает с толку, как в документации сказано: «Они (BottomDrawer) используются только с нижними панелями приложений». Это заставило меня подумать, что мне нужно искать слот BottomDrawer() внутри Scaffold или что я должен вызвать BottomDrawer() внутри BottomAppBar(). В обоих случаях у меня было странное поведение. Вот как я решил проблему. Я надеюсь, что это поможет кому-то, особенно если вы пытаетесь выполнить лабораторное упражнение в модуле 5 макетов в Jetpack Compose из курса Jetpack Compose.
@ExperimentalMaterialApi
@Composable
fun MyApp() {
var selectedItem by rememberSaveable { mutableStateOf(1)}
BottomDrawer(
modifier = Modifier.background(MaterialTheme.colors.onPrimary),
drawerShape = Shapes.medium,
drawerContent = {
Column(Modifier.fillMaxWidth()) {
for(i in 1..6) {
when (i) {
1 -> Row(modifier = Modifier.clickable { }.padding(16.dp)){
Icon(imageVector = Icons.Rounded.Inbox, contentDescription = null)
Text(text = "Inbox")
}
2 -> Row(modifier = Modifier.clickable { }.padding(16.dp)){
Icon(imageVector = Icons.Rounded.Outbox, contentDescription = null)
Text(text = "Outbox")
}
3 -> Row(modifier = Modifier.clickable { }.padding(16.dp)){
Icon(imageVector = Icons.Rounded.Archive, contentDescription = null)
Text(text = "Archive")
}
}
}
}
},
gesturesEnabled = true
) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "Learning Compose Layouts" )
},
actions = {
IconButton(onClick = { /*TODO*/ }) {
Icon(Icons.Filled.Favorite, contentDescription = null)
}
}
)
},
bottomBar = { BottomAppBar(cutoutShape = CircleShape, contentPadding = PaddingValues(0.dp)) {
for (item in 1..4) {
BottomNavigationItem(
modifier = Modifier.clipToBounds(),
selected = selectedItem == item ,
onClick = { selectedItem = item },
icon = {
when (item) {
1 -> { Icon(Icons.Rounded.MusicNote, contentDescription = null) }
2 -> { Icon(Icons.Rounded.BookmarkAdd, contentDescription = null) }
3 -> { Icon(Icons.Rounded.SportsBasketball, contentDescription = null) }
4 -> { Icon(Icons.Rounded.ShoppingCart, contentDescription = null) }
}
}
)
}
}
}
) { innerPadding -> BodyContent(
Modifier
.padding(innerPadding)
.padding(8.dp))
}
}
}