Как правильно применить значения отступов из вложенных скаффолдов в Compose?Android

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Как правильно применить значения отступов из вложенных скаффолдов в Compose?

Сообщение Anonymous »

Я работаю с Compose Multiplatform, и у меня вопрос о правильном шаблоне для вложенных макетов Scaffold.
Мой корневой составной (AppContent) имеет Scaffold, который предоставляет TopAppBar и передает его InternalPadding своим дочерним элементам.
Один из моих дочерних экранов (InventoryScreen) также необходимо использовать свой собственный Scaffold для отображения FloatingActionButton (FAB), который генерирует свои собственные new отступы.
Моя проблема в том, что мой контент внутри InventoryScreen (например, мой OutlinedTextField) теперь имеет чрезмерное верхнее дополнение, как если бы отступ из TopAppBar и дочерний Scaffold объединяются неправильно.
Каков правильный способ применить InnerPadding из родительского Scaffold (для TopAppBar) и также применить paddingValues из дочернего Scaffold (для FAB) без этого чрезмерного интервала?
Результат дочернего экрана
Мой родительский каркас:
package org.pos.kasirin

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.pos.kasirin.core.designsystem.KasirinIcons
import com.pos.kasirin.feature.dashboard.screen.DashboardScreen
import com.pos.kasirin.feature.inventory.screen.CashierScreen
import com.pos.kasirin.feature.inventory.screen.InventoryScreen
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.pos.kasirin.navigation.RootComponent
import org.pos.kasirin.navigation.component.AccountComponent
import org.pos.kasirin.navigation.component.CashierComponent
import org.pos.kasirin.navigation.component.ReportsComponent
import org.pos.kasirin.navigation.component.SettingsComponent

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppContent(component: RootComponent) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()

val activeItem by component.activeItem.subscribeAsState()
val stack by component.stack.subscribeAsState()

ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet(
drawerContainerColor = MaterialTheme.colorScheme.background,
modifier = Modifier.fillMaxWidth(0.75f)
) {
Column(
modifier = Modifier.fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
verticalArrangement = Arrangement.Center,
) {
Icon(
painter = painterResource(KasirinIcons.logo),
contentDescription = "Logo",
modifier = Modifier.padding(horizontal = 24.dp)

)
}

component.drawerItems.forEach { item ->
val isSelected = item.id == activeItem.id
NavigationDrawerItem(
label = { Text(stringResource(item.label)) },
selected = isSelected,
onClick = {
scope.launch { drawerState.close() }
component.onDrawerItemClicked(item)
},
icon = {
Icon(
imageVector = (if (isSelected) item.selectedIcon else item.unselectedIcon),
contentDescription = stringResource(item.label)
)
},
modifier = Modifier.padding(horizontal = 12.dp),
colors = NavigationDrawerItemDefaults.colors(
selectedContainerColor = MaterialTheme.colorScheme.secondary,
selectedIconColor = MaterialTheme.colorScheme.onSecondary,
selectedTextColor = MaterialTheme.colorScheme.onSecondary,
unselectedContainerColor = Color.Transparent,
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
),
shape = RoundedCornerShape(size = 8.dp)
)
}
}
}
}
) {
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
stringResource(activeItem.label),
style = MaterialTheme.typography.titleMedium
)
},
navigationIcon = {
IconButton(onClick = { scope.launch { drawerState.open() } }) {
Icon(
painter = painterResource(KasirinIcons.menu),
contentDescription = "Menu"
)
}
}
)
},
) { innerPadding ->
Children(
stack = stack,
) { child ->
when (val instance = child.instance) {
is RootComponent.Child.Dashboard -> DashboardScreen(instance.component)
is RootComponent.Child.Inventory -> InventoryScreen(
instance.component,
modifier = Modifier.padding(innerPadding)
)

is RootComponent.Child.Reports -> ReportsScreen(instance.component)
is RootComponent.Child.Settings -> SettingsScreen(instance.component)
is RootComponent.Child.Account -> AccountScreen(instance.component)
is RootComponent.Child.Cashier -> CashierScreen(instance.component)
}
}
}
}
}

@Composable
private fun PlaceholderScreen(title: String) {
Box(modifier = Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) {
Text(
text = "Screen: $title",
style = MaterialTheme.typography.titleLarge,
)
}
}

@Composable
fun ReportsScreen(component: ReportsComponent) {
PlaceholderScreen("Reports")
}

@Composable
fun SettingsScreen(component: SettingsComponent) {
PlaceholderScreen("Settings")
}

@Composable
fun AccountScreen(component: AccountComponent) {
PlaceholderScreen("Account")
}

@Composable
fun CashierScreen(component: CashierComponent) {
PlaceholderScreen("Cashier")
}


Эшафот моего ребенка:

package com.pos.kasirin.feature.inventory.screen

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.AddShoppingCart
import androidx.compose.material.icons.filled.Category
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.pos.kasirin.core.designsystem.KasirinIcons
import com.pos.kasirin.core.designsystem.component.ProductCard
import com.pos.kasirin.core.designsystem.component.ProductDisplay
import com.pos.kasirin.core.domain.model.Product
import com.pos.kasirin.feature.inventory.navigation.InventoryComponent
import com.pos.kasirin.feature.inventory.viewmodel.InventoryUiState

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun InventoryScreen(
component: InventoryComponent,
modifier: Modifier = Modifier
) {
val state by component.uiState.collectAsState()
InventoryScreenContent(
state = state,
onAddProduct = component::onAddProductClicked,
onAddCategory = component::onAddCategoryClicked,
onEditProduct = component::onEditProductClicked,
onDeleteProduct = component::onDeleteProductClicked,
onDismissDialog = component::onDismissDialog,
onSaveProduct = component::onSaveProductClicked,
onSaveCategory = component::onSaveCategoryClicked,
onSearchQueryChanged = component::onSearchQueryChanged,
onCategorySelected = component::onCategorySelected,
modifier = modifier
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun InventoryScreenContent(
state: InventoryUiState,
onAddProduct: () -> Unit,
onAddCategory: () -> Unit,
onEditProduct: (Product) -> Unit,
onDeleteProduct: (Product) -> Unit,
onDismissDialog: () -> Unit,
onSaveProduct: (name: String, price: String, stock: String) -> Unit,
onSaveCategory: (name: String) -> Unit,
onSearchQueryChanged: (String) -> Unit,
onCategorySelected: (String?) -> Unit,
modifier: Modifier = Modifier
) {
Scaffold(
modifier = modifier,
floatingActionButton = {
SpeedDialFab(
onAddProductClick = onAddProduct,
onAddCategoryClick = onAddCategory
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = state.searchQuery,
onValueChange = onSearchQueryChanged,
label = { Text("Cari produk...") },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = "Search") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
singleLine = true
)

LazyRow(
modifier = Modifier
.fillMaxWidth(),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
item {
FilterChip(
selected = state.selectedCategoryId == null,
onClick = { onCategorySelected(null) },
label = { Text("Semua") }
)
}

items(state.categories, key = { it.id }) { category ->
FilterChip(
selected = state.selectedCategoryId == category.id,
onClick = { onCategorySelected(category.id) },
label = { Text(category.name) }
)
}
}

LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp),
contentPadding = PaddingValues(bottom = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(state.products, key = { it.id }) { product ->
val productDisplay = ProductDisplay(
id = product.id,
imageUrl = product.imageUrl,
name = product.name,
price = product.sellingPrice,
stock = product.stockQuantity
)

ProductCard(
product = productDisplay,
onClick = { onEditProduct(product) }
)
}
}
}
}

if (state.showProductDialog) {
AddEditProductDialog(
editingProduct = state.editingProduct,
onDismiss = onDismissDialog,
onSave = onSaveProduct
)
}

if (state.showCategoryDialog) {
AddCategoryDialog(
onDismiss = onDismissDialog,
onSave = onSaveCategory
)
}
}

@Composable
private fun SpeedDialFab(
onAddProductClick: () -> Unit,
onAddCategoryClick: () -> Unit
) {
var expanded by remember { mutableStateOf(false) }

Box {
AnimatedVisibility(
visible = expanded,
enter = fadeIn(animationSpec = tween(150)),
exit = fadeOut(animationSpec = tween(150))
) {
Surface(
modifier = Modifier
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
onClick = { expanded = false }
),
) {}
}

Column(
modifier = Modifier.padding(end = 8.dp),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
AnimatedVisibility(
visible = expanded,
enter = fadeIn() + slideInVertically { it / 2 },
exit = fadeOut() + slideOutVertically { it / 2 }
) {
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
ExtendedFloatingActionButton(
text = { Text("Tambah Kategori") },
icon = { Icon(KasirinIcons.category, "Tambah Kategori") },
onClick = {
expanded = false
onAddCategoryClick()
}
)
ExtendedFloatingActionButton(
text = { Text("Tambah Produk") },
icon = { Icon(KasirinIcons.inventory, "Tambah Produk") },
onClick = {
expanded = false
onAddProductClick()
}
)
}
}

FloatingActionButton(
onClick = { expanded = !expanded }
) {
Crossfade(targetState = expanded) { isExpanded ->
if (isExpanded) {
Icon(Icons.Default.Close, contentDescription = "Tutup")
} else {
Icon(Icons.Default.Add, contentDescription = "Tambah")
}
}
}
}
}
}



Подробнее здесь: https://stackoverflow.com/questions/798 ... in-compose
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»