Component에서 사용하는 컬러는 ColorSchemeKeyTokens에서 정의된 enum과 ColorScheme를 매칭해서 사용
internal fun ColorScheme.fromToken(value: ColorSchemeKeyTokens): Color {
return when (value) {
ColorSchemeKeyTokens.Background -> background
ColorSchemeKeyTokens.Error -> error
ColorSchemeKeyTokens.ErrorContainer -> errorContainer
ColorSchemeKeyTokens.InverseOnSurface -> inverseOnSurface
ColorSchemeKeyTokens.InversePrimary -> inversePrimary
ColorSchemeKeyTokens.InverseSurface -> inverseSurface
ColorSchemeKeyTokens.OnBackground -> onBackground
ColorSchemeKeyTokens.OnError -> onError
ColorSchemeKeyTokens.OnErrorContainer -> onErrorContainer
ColorSchemeKeyTokens.OnPrimary -> onPrimary
ColorSchemeKeyTokens.OnPrimaryContainer -> onPrimaryContainer
ColorSchemeKeyTokens.OnSecondary -> onSecondary
ColorSchemeKeyTokens.OnSecondaryContainer -> onSecondaryContainer
ColorSchemeKeyTokens.OnSurface -> onSurface
ColorSchemeKeyTokens.OnSurfaceVariant -> onSurfaceVariant
ColorSchemeKeyTokens.SurfaceTint -> surfaceTint
ColorSchemeKeyTokens.OnTertiary -> onTertiary
ColorSchemeKeyTokens.OnTertiaryContainer -> onTertiaryContainer
ColorSchemeKeyTokens.Outline -> outline
ColorSchemeKeyTokens.OutlineVariant -> outlineVariant
ColorSchemeKeyTokens.Primary -> primary
ColorSchemeKeyTokens.PrimaryContainer -> primaryContainer
ColorSchemeKeyTokens.Scrim -> scrim
ColorSchemeKeyTokens.Secondary -> secondary
ColorSchemeKeyTokens.SecondaryContainer -> secondaryContainer
ColorSchemeKeyTokens.Surface -> surface
ColorSchemeKeyTokens.SurfaceVariant -> surfaceVariant
ColorSchemeKeyTokens.Tertiary -> tertiary
ColorSchemeKeyTokens.TertiaryContainer -> tertiaryContainer
}
}
Material 3 기준으로 정리됨
TopAppBar → (private) SingleRowTopAppBar
CenterAlignedTopAppBar → (private) SingleRowTopAppBar
MediumTopAppBar → (private) TwoRowsTopAppBar
LargeTopAppBar → (private) TwoRowsTopAppBar
Filled button : Button → Filled button
Outlined button : OutlinedButton → Button
FAB / Small / Large / Extended 종류 있음
FAB : FloatingActionButton → Surface + Box
SmallFloatingActionButton → FloatingActionButton
FloatingActionButton(
...
modifier = modifier.sizeIn(
minWidth = FabPrimarySmallTokens.ContainerWidth,
minHeight = FabPrimarySmallTokens.ContainerHeight,
),
)
LargeFloatingActionButton → FloatingActionButton
ExtendedFloatingActionButton → FloatingActionButton → Row + AnimatedVisibility 조합
Card → Surface + Column
ElevatedCard → Card
OutlinedCard → Card
AssistChip → (private) Chip
FilterChip → (private) SelectableChip
InputChip → (private) SelectableChip
SuggestionChip → (private) Chip
(expect) AlertDialog → (internal) AlertDialogImpl
Dialog → DialogWrapper + DialogLayout
DialogProperties : Dialog의 동작을 커스텀시 사용되는 속성
progress indicator 타입
표시 형태
Slider → (private) SliderImpl
RangeSlider → (private) RangeSliderImpl
Switch → (private) SwitchImpl
Checkbox → TriStateCheckbox
TriStateCheckbox → CheckboxImpl
ModalBottomSheet → ModalBottomSheetDialog + Column
Slide-in Menu 형태의 Composable : ModalNavigationDrawer
DrawerState로 Drawer를 제어
화면 하단에 표시하는 알림 형태
showSnackbar()
함수 제공Scaffold의 snackbarHost에 SnackbarHostState를 전달해서 사용 가능
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
icon = { Icon(Icons.Filled.Image, contentDescription = "") },
onClick = {
// Case1. 단일 텍스트 노출
scope.launch {
snackbarHostState.showSnackbar("Snackbar")
}
// Case2. Action이 있는 형태
val result = snackbarHostState
.showSnackbar(
message = "Snackbar",
actionLabel = "Action",
// Defaults to SnackbarDuration.Short
duration = SnackbarDuration.Indefinite
)
when (result) {
SnackbarResult.ActionPerformed -> {
/* Handle snackbar action performed */
}
SnackbarResult.Dismissed -> {
/* Handle snackbar dismissed */
}
}
}
)
}
) { contentPadding ->
// Screen content
}
Column에 스크롤이 필요한 경우, verticalScroll() Modifier를 사용
LazyListScope block으로 컨텐츠를 정의할 DSL을 제공
LazyColumn {
// Add a single item
item {
Text(text = "First item")
}
// Add 5 items
items(5) { index ->
Text(text = "Item: $index")
}
// Add another single item
item {
Text(text = "Last item")
}
}
LazyColumn {
items(messages) { message ->
MessageRow(message)
}
}
LazyVerticalGrid
→ (internal) LazyGrid
LazyHorizontalGrid
→ (internal) LazyGrid
GridCells
최소 크기 이상의 공간
이 있고 모든 여분의 공간이 균등하게 분포되어 있는 조건 정의정확한 크기의 공간
을 차지한다는 조건 정의LazyGridScope block으로 컨텐츠를 정의할 DSL을 제공
LazyVerticalStaggeredGrid → (internal) LazyStaggeredGrid
LazyHorizontalStaggeredGrid → (internal) LazyStaggeredGrid
StaggeredGridCells
컨텐츠 가장자리에 패딩을 추가할 때 사용
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
// ...
}
Arrangement.spacedBy()를 사용하여 내용 간의 간격 정의
LazyColumn(
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
// ...
}
기본적으로 각 item의 상태는 list/grid에서 항목의 위치에 대해 키가 지정됨. 다만, 데이터 세트가 변경되면 위치가 변경된 항목은 기억된 상태(remembered state)를 잃게 된다.
LazyColumn {
items(
items = messages,
key = { message ->
// Return a stable + unique key for the item
message.id
}
) { message ->
MessageRow(message)
}
}
animateItemPlacement API를 사용
LazyColumn {
items(books, key = { it.id }) {
Row(Modifier.animateItemPlacement()) {
// ...
}
}
}
stickyHeader API를 사용
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListWithHeader(items: List<Item>) {
LazyColumn {
stickyHeader {
Header()
}
...
}
}
LazyListState 상태에 있는 API를 활용
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun MessageList(messages: List<Message>) {
Box {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
// firstVisibleItemIndex이 첫 번째 항목을 지나면 버튼을 표시
// 불필요한 composition을 최소화하기 위해 remember+derivedStateOf를 사용
val showButton by remember {
derivedStateOf {
listState.firstVisibleItemIndex > 0
}
}
AnimatedVisibility(visible = showButton) {
ScrollToTopButton()
}
}
}
동일한 composition에서 이벤트를 처리할 필요하다면 snapshotFlow()를 사용
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.map { index -> index > 0 }
.distinctUntilChanged()
.filter { it }
.collect {
MyAnalyticsService.sendScrolledPastFirstItemEvent()
}
}
LazyListState 상태에 있는 API를 활용
@Composable
fun MessageList(messages: List<Message>) {
val listState = rememberLazyListState()
// Remember a CoroutineScope to be able to launch
val coroutineScope = rememberCoroutineScope()
LazyColumn(state = listState) {
// ...
}
ScrollToTopButton(
onClick = {
coroutineScope.launch {
// 첫 번째 항목으로 스크롤 애니메이션 적용
listState.animateScrollToItem(index = 0)
}
}
)
}
Compose 지원은 Paging 3.0 이상에서만 제공
[Flow<PagingData
@Composable
fun MessageList(pager: Pager<Int, Message>) {
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
LazyColumn {
items(
lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.id }
) { index ->
val message = lazyPagingItems[index]
if (message != null) {
MessageRow(message)
} else {
MessagePlaceholder()
}
}
}
}
LazyColumn {
items(elements, contentType = { it.type }) {
// ...
}
}
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024