티스토리 뷰
# 개요
바텀시트는 모바일 앱에서는 흔하게 볼 수 있는 컴포넌트 입니다. 당연하게도 Compose는 BottomSheet를 제공하고 있습니다. 구현 시 아래 3가지 컴포저블 중 하나를 사용하면 됩니다.
- ModalBottomSheet
- ModalBottomSheetLayout
- BottomSheetScaffold
Android 개발자 Compose 가이드로 소개되어 있는 ModalBottomSheet부터 살펴보겠습니다.
#1> ModalBottomSheet 를 사용해 바텀시트 노출하기
가장 간단한 형태로, 바텀시트가 노출되어야 하는 시점에 이 Composable이 노출되게 하면 됩니다.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ModalBottomSheetScreen() {
var isShowBottomSheet by remember { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxSize()) {
Button(
modifier = Modifier.align(Alignment.Center),
onClick = { isShowBottomSheet = !isShowBottomSheet }
) {
if (isShowBottomSheet) {
ModalBottomSheet(
scrimColor = Color.Cyan.copy(alpha = 0.3f),
onDismissRequest = { isShowBottomSheet = !isShowBottomSheet },
content = { BottomSheetContent() }
)
Text(text = "Hide Bottom Sheet")
} else {
Text(text = "Show Bottom Sheet")
}
}
}
}
심플하죠? 간단하지만 쓸 수 없습니다. 왜냐하면 이녀석, 하단 네비게이션 바를 가려버리거든요.
스크린의 완전 바닥부터 노출이 됩니다. 아래와 같이 인셋을 설정하면 조금 낫습니다.
WindowCompat.setDecorFitsSystemWindows(window, false)
그래도 내려갈 때 하단 네비게이션 바를 가립니다.
아직 포기하긴 이릅니다. 총알이 두발 남았거든요.
#2> ModalBottomSheetLayout 를 사용해 바텀시트 노출하기
화면의 나머지 부분과의 상호작용을 차단하는 바텀시트를 제공한다. [링크]
모달 바텀시트를 제공하는 컨테이너 형태의 Composable 입니다. 바텀시트와 컨텐츠를 분리해서 선언할 수 있습니다. 이녀석, 네비게이션 바를 가리지 않습니다.
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ModalBottomSheetLayoutScreen() {
var skipHalfExpanded by remember { mutableStateOf(false) }
val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
skipHalfExpanded = skipHalfExpanded
)
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetState = state,
sheetContent = {
LazyColumn {
items(50) {
ListItem(
text = { Text("Item $it") },
icon = {
Icon(
Icons.Default.Favorite,
contentDescription = "Localized description"
)
}
)
}
}
}
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
Modifier.toggleable(
value = skipHalfExpanded,
role = Role.Checkbox,
onValueChange = { checked -> skipHalfExpanded = checked }
)
) {
Checkbox(checked = skipHalfExpanded, onCheckedChange = null)
Spacer(Modifier.width(16.dp))
Text("Skip Half Expanded State")
}
Spacer(Modifier.height(20.dp))
Button(
onClick = {
scope.launch {
state.show()
}
}
) {
Text("Click to show sheet")
}
}
}
}
위 코드를 실행하시면 샘플 동작을 확인하실 수 있습니다. 주요 특징은 다음과 같습니다.
- 반만 열림 상태 허용여부 설정 가능.
- 바텀시트 핸들 제스쳐를 허용할 것인지 설정 가능.
이렇게 해답을 찾은 것 같았으나, 역시 아웃입니다. 딤 영역 터치 제어가 불가능하기 때문이죠.
다시말해 [ 바텀시트의 확인 버튼 클릭 시에만 닫힘 ] 과 같은 동작을 구현하기 어렵습니다. 왜인지 모르겠으나 구글에서 관련 프로퍼티를 제공하지 않습니다.
그렇다고 불가능 하진 않습니다. 시트 상태값 변경 Callback 을 받아 트릭을 사용해 처리가 가능합니다.
#3> BottomSheetScaffold 를 사용해 바텀시트 노출하기
화면의 메인 UI 영역과 공존하며 두 영역을 동시에 보고 상호작용 할 수 있다. [링크]
모달이 아닌 바텀시트를 제공하는 컨테이너 형태의 Composable 입니다. 네이버 지도와 같은 서비스에서 마커 클릭 시 올라오는 형태의 바텀시트를 생각하시면 좋습니다.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomSheetScaffoldScreen() {
val state = rememberBottomSheetScaffoldState(
bottomSheetState = rememberStandardBottomSheetState(
initialValue = SheetValue.Hidden,
skipHiddenState = false
)
)
val scope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = state,
sheetContent = { BottomSheetContent() },
// if set below setting, skip PartialExpand State.
//sheetPeekHeight = 0.dp
) {
Box {
Column(
modifier = Modifier.align(Alignment.Center),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(text = "Current State is ${state.bottomSheetState.currentValue}")
Button(
onClick = {
scope.launch {
state.bottomSheetState.show()
}
}
) {
Text(text = "Show Bottom Sheet")
}
Button(
onClick = {
scope.launch {
state.bottomSheetState.hide()
}
}
) {
Text(text = "Hide Bottom Sheet")
}
Button(
onClick = {
scope.launch {
state.bottomSheetState.expand()
}
}
) {
Text(text = "Expand Bottom Sheet")
}
Button(
onClick = {
scope.launch {
state.bottomSheetState.partialExpand()
}
}
) {
Text(text = "PartialExpand Bottom Sheet")
}
}
}
}
}
바텀시트는 아래 3가지 상태를 가집니다.
- Hidden
- Expanded
- PariallyExpanded
기본적으로 Hidden 상태는 제공되지 않으며, state.hide() 호출시 Exception이 발생합니다. Hidden 상태를 쓰고자 하는 경우, 시트 상태 초기화 시 skipHiddenState를 명시적으로 false 로 초기화 해줘야 합니다.
또한, 반만 열림상태를 건너뛰고 싶은 경우 sheetPeekHeight 값을 0.dp 로 설정해주시면 됩니다.
이녀석을 사용하면, 물론 몇가지 트릭이 필요하지만, View 기반의 바텀시트와 동일한 기능을 제공할 수 있습니다.
# 마치며
Compose 를 사용하면서 자주 하게되는 말이 있습니다.
" 어? 이게 안된다고? " 혹은 " 어? 이게 된다고? "
이번 BottomSheet 도입 작업은 위 두 감탄의 연속이었습니다. 하지만 이겨 내야합니다. Compose가 주는 생산성이 너무 강력해서 다시 View 기반으로 돌아갈 수 없거든요.
이 포스팅이 Compose로 바텀시트를 구현하겠다는 생각을 가진 분들에게 조금이나마 도움이 되길 바랍니다.
내용에 오류가 있거나 궁금한 점은 댓글로 남겨주세요. 감사합니다
'Programming > Android' 카테고리의 다른 글
Android :: Android Studio 앱 실행 시 Run 탭 안보이게 하는방법 (0) | 2023.06.10 |
---|---|
Android :: Android Studio Live Template 적용방법 (0) | 2023.05.05 |
Android :: 위치정보 파헤치기 / FusedLocationProvider 개념과 사용법 (8) | 2021.04.05 |
Android :: 안드로이드 TextView 자동 글자크기 변경 / ... 표시 / 흐르는 효과 (0) | 2020.05.03 |
Android :: 해시키, 키해시(Key Hash)의 개념과 활용 / 생성방법 (3) | 2020.04.10 |
- Total
- Today
- Yesterday
- Kotlin
- 코틀린 기초
- 코딩
- 개발자
- 안드로이드 컴포즈
- 영어회화
- 영어발음
- LiveTemplate
- 자바
- 코틀린
- 프로젝트오일러
- 프로그래밍
- 런탭
- 안드로이드 스튜디오 라이브 템플릿
- Java
- 코틀린 기초강의
- Android
- kotlin 기초
- 안스 템플릿
- 컴포즈 바텀시트
- 안드로이드
- android studio
- php
- 안드로이드 스튜디오
- 코딩문제
- compose bottomsheet
- Programming
- 안드로이드 바텀시트
- 문제풀이
- live template
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |