안드로이드에서 Side sheet를 Navigation drawer라고 한다.
Material Design에 포함된 Navigation drawer는 두 가지 유형이 있다.

내가 구현한 건 Modal 형식의 사이트시트이다.
사실 앱 같은 소형 스크린에서는 바텀시트를 활용하는 게 UI적으로는 더 좋은 디자인이지만, 사이드시트는 이미 여러 앱에서 흔하게 사용하고 있고, 상단 AppBar의 햄버거 버튼을 누르면 사이드시트가 나와, 사이드시트에서 사용자 계정 관련 설정을 보여주고 싶었다.
Navigation drawer 사용 시 고려할 점
1. 오른쪽에서 왼쪽으로 쓰는 RTL 언어의 경우 사이드시트가 모든 요소가 반전된 상태로 창의 왼쪽부터 쓰기
2. Navigation drawer는 기본 콘텐츠와 독립적으로 수직 스크롤을 할 수 있지만, 수평으로는 스크롤 불가능
구현설명은 다음 코드의 주석 참고하면 된다.
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.DrawerValue
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.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@Composable
fun test(
content: @Composable (PaddingValues) -> Unit
) {
// drawer이 열리고 닫히는 방식을 제어
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
// drawer의 open과 close 함수에 엑세스하기 위해 CoroutineScope 필요
val scope = rememberCoroutineScope()
ModalNavigationDrawer( // 창 만들기
/* Modal 사용 설정 */
drawerState = drawerState,
drawerContent = { // 필수값
ModalDrawerSheet( // 창에 스타일 제공
/* Modal 모양, 배경색, 그림자 효과 등 */
) {
/* Drawer sheet 안에 들어올 content들 */
// 예시
Spacer(Modifier.height(12.dp))
Text(
"Drawer Title",
modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.titleLarge
)
Text("Section 1", modifier = Modifier.padding(20.dp))
HorizontalDivider() // 섹션 구분
NavigationDrawerItem( // drawer의 개별 항목
label = { Text("Item 1") },
selected = false,
onClick = { /* Handle click */ }
)
NavigationDrawerItem(
label = { Text("Item 2") },
selected = false,
onClick = { /* Handle click */ }
)
}
},
gesturesEnabled = !drawerState.isClosed // 창이 드래그에 응답하는지 여부 => drawer이 열려 있을 때만 드래그 사용 가능
) {
/* drawer가 열리는 화면단의 content들 구현 */
// 에시
Scaffold(
topBar = {
TopAppBar(
title = { Text("Navigation Drawer Example") },
navigationIcon = {
IconButton(onClick = {
scope.launch {
if (drawerState.isClosed) {
drawerState.open()
} else {
drawerState.close()
}
}
}) {
Icon(Icons.Default.Menu, contentDescription = "Menu")
}
}
)
}
) { innerPadding ->
content(innerPadding)
}
}
}
![]() | ![]() |
|---|