Jetpack Compose는 디지털 인터페이스를 만들기 위한 포괄적인 디자인 시스템인 Material Design 구현을 제공합니다. Material 구성요소(버튼, 카드, 스위치 등) 및 레이아웃(예: Scaffold
)은 구성 가능한 함수로 사용할 수 있습니다.
머티리얼 구성요소는 사용자 인터페이스를 만드는 대화형 구성요소입니다. Compose는 이러한 여러 구성요소를 바로 제공합니다. 사용할 수 있는 항목을 보려면 Compose Material API 참조를 확인하세요.
Material 구성요소는 앱의 MaterialTheme
에서 제공하는 값을 사용합니다.
@Composable
fun MyApp() {
MaterialTheme {
// Material Components like Button, Card, Switch, etc.
}
}
테마 설정에 관한 자세한 내용은 Compose의 디자인 시스템 가이드를 참고하세요.
콘텐츠 슬롯
내부 콘텐츠(텍스트 라벨, 아이콘 등)를 지원하는 Material 구성요소는 구성 가능한 콘텐츠를 허용하는 일반 람다인 '슬롯'과 함께 공개 상수(예: 크기, 패딩)를 제공하여 Material 사양과 일치하도록 내부 콘텐츠 배치를 지원하는 경향이 있습니다.
Button
을 예로 들 수 있습니다.
Button(
onClick = { /* ... */ },
// Uses ButtonDefaults.ContentPadding by default
contentPadding = PaddingValues(
start = 20.dp,
top = 12.dp,
end = 20.dp,
bottom = 12.dp
)
) {
// Inner content including an icon and a text label
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Like")
}
그림 1. content
슬롯과 기본 패딩(왼쪽)을 사용하는 Button
과 맞춤 contentPadding
(오른쪽)을 제공하는 content
슬롯을 사용하는 Button
Button
에는 일반 content
후행 람다 슬롯이 있고 이 슬롯은 RowScope
를 사용하여 행에서 콘텐츠 컴포저블을 배치합니다. 내부 콘텐츠에 패딩을 적용하는 contentPadding
매개변수도 있습니다. ButtonDefaults
를 통해 제공된 상수나 맞춤 값을 사용할 수 있습니다.
또 다른 예로는 ExtendedFloatingActionButton
이 있습니다.
ExtendedFloatingActionButton(
onClick = { /* ... */ },
icon = {
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite"
)
},
text = { Text("Like") }
)
그림 2. icon
과 text
슬롯을 사용하는 ExtendedFloatingActionButton
일반 content
람다가 아닌 ExtendedFloatingActionButton
에는 icon
과 text
라벨용 슬롯 2개가 있습니다. 각 슬롯은 구성 가능한 일반 콘텐츠를 지원하지만 구성요소는 이러한 내부 콘텐츠 배치 방식에 관해 독단적입니다. 내부적으로 패딩과 정렬, 크기를 처리합니다.
Scaffold
Compose는 머티리얼 구성요소를 일반 화면 패턴으로 결합하는 편리한 레이아웃을 제공합니다. Scaffold
와 같은 컴포저블은 다양한 구성요소와 기타 화면 요소를 위한 슬롯을 제공합니다.
화면 콘텐츠
Scaffold
에는 일반 content
후행 람다 슬롯이 있습니다. 람다는 콘텐츠 루트에 적용해야 하는 PaddingValues
인스턴스를 수신(예: Modifier.padding
을 통해)하여 상단과 하단 막대가 있으면 이를 오프셋합니다.
Scaffold(/* ... */) { contentPadding ->
// Screen content
Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}
앱 바
Scaffold
는 상단 앱 바나 하단 앱 바의 슬롯을 제공합니다. 컴포저블의 배치는 내부적으로 처리됩니다.
topBar
슬롯과 TopAppBar
를 사용할 수 있습니다.
Scaffold(
topBar = {
TopAppBar { /* Top app bar content */ }
}
) {
// Screen content
}
bottomBar
슬롯과 BottomAppBar
를 사용할 수 있습니다.
Scaffold(
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
이러한 슬롯은 BottomNavigation
과 같은 다른 머티리얼 구성요소에 사용할 수 있습니다.
맞춤 컴포저블을 사용할 수도 있습니다. 예를 들어 Owl 샘플에서 온보딩 화면을 살펴보세요.
플로팅 작업 버튼
Scaffold
는 플로팅 작업 버튼 슬롯을 제공합니다.
floatingActionButton
슬롯과 FloatingActionButton
을 사용할 수 있습니다.
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
}
) {
// Screen content
}
FAB 컴포저블의 하단 배치는 내부적으로 처리됩니다. floatingActionButtonPosition
매개변수를 사용하여 가로 위치를 조정할 수 있습니다.
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to FabPosition.End
floatingActionButtonPosition = FabPosition.Center
) {
// Screen content
}
Scaffold
컴포저블의 bottomBar
슬롯을 사용하고 있다면 isFloatingActionButtonDocked
매개변수를 사용하여 FAB를 하단 앱 바와 겹칠 수 있습니다.
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to false
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
그림 3. floatingActionButton
슬롯과 bottomBar
슬롯을 사용하는 Scaffold
. isFloatingActionButtonDocked
매개변수는 상단은 false
, 하단은 true
로 설정됩니다.
BottomAppBar
는 모든 Shape
를 허용하는 cutoutShape
매개변수를 사용하여 FAB 컷아웃을 지원합니다. 고정된 구성요소에 사용되는 같은 Shape
를 제공하는 것이 좋습니다. 예를 들어 FloatingActionButton
은 모서리 크기가 50%인 MaterialTheme.shapes.small
을 shape
매개변수의 기본값으로 사용합니다.
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar(
// Defaults to null, that is, No cutout
cutoutShape = MaterialTheme.shapes.small.copy(
CornerSize(percent = 50)
)
) {
/* Bottom app bar content */
}
}
) {
// Screen content
}
그림 4. BottomAppBar
와 고정된 FloatingActionButton
이 있는 Scaffold
. BottomAppBar
에는 FloatingActionButton
에서 사용하는 Shape
와 일치하는 맞춤 cutoutShape
가 있습니다.
스낵바
Scaffold
는 스낵바를 표시하는 방법을 제공합니다.
SnackbarHostState
속성이 포함된 ScaffoldState
를 통해 제공됩니다. rememberScaffoldState
를 사용하여 scaffoldState
매개변수로 Scaffold
에 전달해야 하는 ScaffoldState
인스턴스를 만들 수 있습니다. SnackbarHostState
를 통해 showSnackbar
함수에 액세스할 수 있습니다. 이 정지 함수는 CoroutineScope
가 필요하고(예: rememberCoroutineScope
사용) UI 이벤트에 대한 응답으로 호출되어 Scaffold
내의 Snackbar
를 표시할 수 있습니다.
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
onClick = {
scope.launch {
scaffoldState.snackbarHostState
.showSnackbar("Snackbar")
}
}
)
}
) {
// Screen content
}
선택적 작업을 제공하고 Snackbar
의 지속 시간을 조정할 수 있습니다.
snackbarHostState.showSnackbar
함수는 추가 actionLabel
및 duration
매개변수를 허용하고 SnackbarResult
를 반환합니다.
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
onClick = {
scope.launch {
val result = scaffoldState.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 */
}
}
}
}
)
}
) {
// Screen content
}
snackbarHost
매개변수를 사용하여 맞춤 Snackbar
를 제공할 수 있습니다. 자세한 내용은 SnackbarHost API reference docs
를 참고하세요.
창
Scaffold
는 모달 탐색 창 슬롯을 제공합니다. 컴포저블의 드래그 가능한 시트와 레이아웃은 내부적으로 처리됩니다.
ColumnScope
를 사용하여 열에서 창 콘텐츠 컴포저블을 배치하는 drawerContent
슬롯을 사용할 수 있습니다.
Scaffold(
drawerContent = {
Text("Drawer title", modifier = Modifier.padding(16.dp))
Divider()
// Drawer items
}
) {
// Screen content
}
Scaffold
는 다양한 추가 창 매개변수를 허용합니다. 예를 들어 drawerGesturesEnabled
매개변수를 사용하여 창이 드래그에 응답하는지 여부를 전환할 수 있습니다.
Scaffold(
drawerContent = {
// Drawer content
},
// Defaults to true
drawerGesturesEnabled = false
) {
// Screen content
}
프로그래매틱 방식으로 창을 여닫는 작업은 ScaffoldState
를 통해 실행되는데 ScaffoldState에는 scaffoldState
매개변수로 Scaffold
에 전달해야 하는 DrawerState
속성이 포함되어 있습니다. DrawerState
를 통해 현재 창 상태와 관련된 속성뿐만 아니라 open
및 close
함수에도 액세스할 수 있습니다. 이러한 정지 함수는 CoroutineScope
가 필요하고(예: rememberCoroutineScope
사용) UI 이벤트에 응답하여 호출될 수 있습니다.
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
// Drawer content
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Open or close drawer") },
onClick = {
scope.launch {
scaffoldState.drawerState.apply {
if (isClosed) open() else close()
}
}
}
)
}
) {
// Screen content
}
모달 창
Scaffold
없이 모달 탐색 창을 구현하려면 ModalDrawer
컴포저블을 사용하면 됩니다. Scaffold
와 비슷한 창 매개변수를 허용합니다.
val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
하단 탐색 창을 구현하려면 BottomDrawer
컴포저블을 사용하면 됩니다.
val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
하단 시트
표준 하단 시트를 구현하려면 BottomSheetScaffold
컴포저블을 사용하면 됩니다. Scaffold
와 비슷한 매개변수(예: topBar
, floatingActionButton
, snackbarHost
)를 허용합니다. 하단 시트를 표시하는 방법을 제공하는 추가 매개변수가 포함되어 있습니다.
ColumnScope
를 사용하여 열에서 시트 콘텐츠 컴포저블을 배치하는 sheetContent
슬롯을 사용할 수 있습니다.
BottomSheetScaffold(
sheetContent = {
// Sheet content
}
) {
// Screen content
}
BottomSheetScaffold
는 여러 추가 시트 매개변수를 허용합니다. 예를 들어 sheetPeekHeight
매개변수를 사용하여 시트의 미리보기 높이를 설정할 수 있습니다. sheetGesturesEnabled
매개변수를 사용하여 창이 드래그에 응답하는지 여부를 전환할 수도 있습니다.
BottomSheetScaffold(
sheetContent = {
// Sheet content
},
// Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
sheetPeekHeight = 128.dp,
// Defaults to true
sheetGesturesEnabled = false
) {
// Screen content
}
프로그래매틱 방식으로 시트를 확장하고 축소하는 작업은 BottomSheetState
속성이 포함된 BottomSheetScaffoldState
를 통해 실행됩니다. rememberBottomSheetScaffoldState
를 사용하여 scaffoldState
매개변수로 BottomSheetScaffold
에 전달해야 하는 BottomSheetScaffoldState
인스턴스를 만들 수 있습니다. BottomSheetState
를 통해 현재 시트 상태와 관련된 속성뿐만 아니라 expand
및 collapse
함수에도 액세스할 수 있습니다. 이러한 정지 함수는 CoroutineScope
가 필요하고(예: rememberCoroutineScope
사용) UI 이벤트에 응답하여 호출될 수 있습니다.
val scaffoldState = rememberBottomSheetScaffoldState()
val scope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = scaffoldState,
sheetContent = {
// Sheet content
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Expand or collapse sheet") },
onClick = {
scope.launch {
scaffoldState.bottomSheetState.apply {
if (isCollapsed) expand() else collapse()
}
}
}
)
}
) {
// Screen content
}
모달 하단 시트를 구현하려면 ModalBottomSheetLayout
컴포저블을 사용하면 됩니다.
val sheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
// Sheet content
}
) {
// Screen content
}
배경화면
배경화면을 구현하려면 BackdropScaffold
컴포저블을 사용하면 됩니다.
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)
BackdropScaffold
는 여러 추가 배경화면 매개변수를 허용합니다. 예를 들어 peekHeight
및 headerHeight
매개변수를 사용하여 후면 레이어의 미리보기 높이와 전면 레이어의 최소 비활성 높이를 설정할 수 있습니다.
gesturesEnabled
매개변수를 사용하여 배경화면이 드래그에 응답하는지 여부를 전환할 수도 있습니다.
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
},
// Defaults to BackdropScaffoldDefaults.PeekHeight
peekHeight = 40.dp,
// Defaults to BackdropScaffoldDefaults.HeaderHeight
headerHeight = 60.dp,
// Defaults to true
gesturesEnabled = false
)
프로그래매틱 방식으로 배경화면을 표시하고 숨기는 작업은 BackdropScaffoldState
를 통해 실행됩니다. rememberBackdropScaffoldState
를 사용하여 scaffoldState
매개변수로 BackdropScaffold
에 전달해야 하는 BackdropScaffoldState
인스턴스를 만들 수 있습니다. BackdropScaffoldState
를 통해 현재 배경화면 상태와 관련된 속성뿐만 아니라 reveal
및 conceal
함수에도 액세스할 수 있습니다. 이러한 정지 함수는 CoroutineScope
가 필요하고(예: rememberCoroutineScope
사용) UI 이벤트에 응답하여 호출될 수 있습니다.
val scaffoldState = rememberBackdropScaffoldState(
BackdropValue.Concealed
)
val scope = rememberCoroutineScope()
BackdropScaffold(
scaffoldState = scaffoldState,
appBar = {
TopAppBar(
title = { Text("Backdrop") },
navigationIcon = {
if (scaffoldState.isConcealed) {
IconButton(
onClick = {
scope.launch { scaffoldState.reveal() }
}
) {
Icon(
Icons.Default.Menu,
contentDescription = "Menu"
)
}
} else {
IconButton(
onClick = {
scope.launch { scaffoldState.conceal() }
}
) {
Icon(
Icons.Default.Close,
contentDescription = "Close"
)
}
}
},
elevation = 0.dp,
backgroundColor = Color.Transparent
)
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)