[Android/Compose] Compose에서 BottomSheet 만드는 방법 (ModalBottomSheet)

곽의진·2024년 4월 18일
3
post-thumbnail
post-custom-banner

오늘은 Compose에서 ModalBottomSheet를 만드는 방법에 대해 포스팅하고자합니다.

해당 예제들은 Material 3 환경에서 제작된 점 참고해주시면 감사하겠습니다

ModalBottomSheet Composable 사용하기

보통 Android 기본 디자인 시스템을 활용하여 BottomSheet를 제작할때는 ModalBottomSheet 키워드가 들어간 컴포저블을 활용합니다.
대표적으로 ModalBottomSheetLayout과 ModalBottomSheet가 있는데요!!

ModalBottomSheet란?

ModalBottomSheet란 안드로이드에서 BottomSheet을 구현하기 위해 제공하는 컴포저블입니다.

아래 예제를 통해 같이 살펴보시죠!

ModalBottomSheet(onDismissRequest = { /* 바텀 시트가 Dismiss된 경우 호츌*/ }) {
    // Sheet content
}

기본적인 형태를 살펴본다면 ModalBottomSheet는 onDismissRequest를 람다를 매개변수로 필수 구현해야하는 컴포저블임을 알 수 있습니다.

내부 Sheet content는 기본적으로 Column Scope로 되어있기 때문에 수직적인 구조의 컴포저블을 간편하게 배치 할 수 있습니다.

ModalBottomSheet의 특징

그런데 보통 BottomSheet의 특정 조건에서만 보여져야하는 특성을 가지고 있습니다. 그렇기 때문에 보통 Boolean State 변수를 통해 Visible을 관리하게 됩니다.

// 예시
var showBottomSheet by remember { mutableStateOf(false) }
if (showBottomSheet) {
    ModalBottomSheet(
        onDismissRequest = {
            showBottomSheet = false
        }
      ) {
        // Sheet content
        Text("Hello")
        Spacer(modifier = Modifier.height(100.dp))
    }
}

ModalBottomSheet SheetState

하지만 위와 같은 Boolean 변수 만을 사용해서는 제한적인 컨트롤만 가능합니다.

예를 들어 버튼을 통해서 BottomSheet를 숨기는 작업을 하고 싶을 때 showBottomSheet를 false로 설정만 하게 된다면 말 그대로 BottomSheet가 축소되는 것이 아닌 바로 사라져버리는 부자연스러운 현상이 발생되는 것이죠 ㅠ.ㅠ

if (showBottomSheet) {
        ModalBottomSheet(
            onDismissRequest = {
                showBottomSheet = false
            }
        ) {
            // Sheet content
            Button(onClick = {
                showBottomSheet = false
            }) {
                Text("Hide bottom sheet")
            }
            Spacer(modifier = Modifier.height(100.dp))
        }
    }

그렇다면 어떤 방법을 사용해야 자연스러운 BottomSheet의 동작 컨트롤이 가능할까요?

ModalBottomSheet는 또한 확장과 축소 동작의 컨트롤을 SheetState를 통해서 관리하며 rememberSheetState 인스턴스를 통해서 사용할 수 있습니다!!

아래 예제를 같이 보시죠 허허

val sheetState = rememberModalBottomSheetState()
val scope = rememberCoroutineScope()
var showBottomSheet by remember { mutableStateOf(false) }
Scaffold(
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show bottom sheet") },
            icon = { Icon(Icons.Filled.Add, contentDescription = "") },
            onClick = {
                showBottomSheet = true
            }
        )
    }
) { contentPadding ->
    // Screen content

    if (showBottomSheet) {
        ModalBottomSheet(
            onDismissRequest = {
                showBottomSheet = false
            },
            sheetState = sheetState
        ) {
            // Sheet content
            Button(onClick = {
                scope.launch { sheetState.hide() }.invokeOnCompletion {
                    if (!sheetState.isVisible) {
                        showBottomSheet = false
                    }
                }
            }) {
                Text("Hide bottom sheet")
                Spacer(modifier = Modifier.height(100.dp))
            }
        }
    }
}

해당 예제에서 coroutine scope을 사용한 이유는 hide()가 suspend 함수이기 때문입니다.

또한 invokeOnCompletion 을 통해 hide(BottomSheet가 축소되는 동작과 애니메이션) 함수가 완료되면 기존에 활용하연 showBottomSheet 변수 또한 false로 초기화를 하는 것입니다.

이렇게 되면 이제 자연스러움 BottomSheet의 동작을 컨트롤 할 수 있게 됩니다!!

이상으로 포스팅을 마치도록 하겠습니다 ㅎㅎ 감사합니다!

profile
Android Developer
post-custom-banner

0개의 댓글