Jetpack Compose는 Android 프로그래밍의 미래가 될 수 있습니다!
대부분의 기능과 컴포저블(Composable)은 사용하기 쉽고 이해하기 쉽습니다. 또한 엄청난 수의 속성을 가지고 있습니다.
이번 시간에는 Scaffold에 대해 알아보려고 합니다.
Scaffold는 무엇이고 어떻게 사용하는 것일까요?
Scaffold는 Material Application에서 사용되는 기본 구성 가능 함수(Composable function)입니다.
UI관련 함수는 모두 Composable function이어야 합니다.
이 구성 요소는 올바른 레이아웃 동작이 보장되는 응용 프로그램 화면을 구성하기 위해 여러 재료 구성 요소를 결합하는 간단한 방법을 제공합니다. (예 : 플로팅 액션 버튼 상단 스낵바의 올바른 위치, 애니메이션 및 서랍 상태 등)
Scaffold에는 TopBar, BottomBar, Snackbar, FloatingActionButton 및 Drawer용 슬롯이 있습니다.
사용자 정의하려면 많은 매개변수가 필요하지만 두려워할 필요는 없습니다.
대부분은 매우 명확하고 간단한 설명으로 이해할 수 있습니다.
@Composable
fun Scaffold(
modifier: Modifier = Modifier,
scaffoldState: ScaffoldState = rememberScaffoldState(),
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
isFloatingActionButtonDocked: Boolean = false,
drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
drawerGesturesEnabled: Boolean = true,
drawerShape: Shape = MaterialTheme.shapes.large,
drawerElevation: Dp = DrawerDefaults.Elevation,
drawerBackgroundColor: Color = MaterialTheme.colors.surface,
drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
drawerScrimColor: Color = DrawerDefaults.scrimColor,
backgroundColor: Color = MaterialTheme.colors.background,
contentColor: Color = contentColorFor(backgroundColor),
content: @Composable (PaddingValues) -> Unit
)
- modifier - 외부에서 Scaffold 루트의 속성을 수정하는데 사용되는 선택적 매개변수
- scaffoldState - 화면의 상태를 기억합니다. ScaffoldState에는 drawer(이것이 열렸는지 닫혔는지) 및 snackbarHost(snackbar 표시 여부)에 대한 정보가 포함되어 있습니다. state와 상호작용할 필요가 없으면 (예: snackbar표시 또는 drawer 열기) 그대로 두면 됩니다.
- floatingActionButtonPosition - floating action 버튼의 위치, 가능한 값은 Center 또는 End입니다.
- isFloatingActionButtonDocked - Boolean 매개변수, floating action 버튼이 높이의 절반만큼 하단 bar와 겹쳐야 하는 경우 true 설정, default는 false. 사용된 하단 bar가 없는 경우 이 매개변수는 무시됩니다.
- drawerGestureEnabled - drawer가 제스처를 통해 상호 작용할 수 있는지 여부
- drawerShape - drawer의 모양
- drawerElevation - drawer의 elevation
- drawerBackgroundColor - drawer 시트에 사용할 배경색
- drawerContentColor - drawer 시트 내부에 사용할 내용의 색상
- drawerScrimColor - drawer가 열렸을 때 내용물을 가리는 scrim의 색상
- backgroundColor - Scaffold 본체의 배경색, 대부분의 경우 이 매개변수를 기본값으로 둡니다.
- contentColor - Scaffold body의 내용 색상, 대부분의 경우 이 매개변수를 기본값으로 둡니다.
이제 중요하고 간단한 나머지 언급하지 않은 매개변수에 대해 논의해보겠습니다.
TopBar
TopBar는 선택적 매개변수입니다.
정의에서 볼 수 있듯이 화면 상단의 슬롯을 채울 Composable function을 제공해야 합니다.
사전 정의된 TopAppBar Composable을 사용하거나 자체 구현을 사용하여 사용자 정의 디자인과 매치시킬 수 있습니다.
기본값은 빈 람다이며 이 경우 topBar는 사용되지 않습니다.
아래 코드는 topBar 매개변수에 전달된 TopAppBar를 구현합니다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp {
MainContent()
}
}
}
}
@Composable
fun MyApp(content: @Composable () -> Unit) {
MovieappTheme {
// A surface container using the 'background' color from the theme
Scaffold( topBar = {
TopAppBar(
title = { Text(text = "Title text") },
navigationIcon = {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
},
actions = {
Icon(imageVector = Icons.Default.Favorite, contentDescription = "Favorite")
Icon( imageVector = Icons.Default.Search, contentDescription = "Search")
}
)
},) {
content()
}
}
}
@Composable
fun MainContent() {
Surface(color = MaterialTheme.colors.background) {
Text(text = "Hello")
}
}
Scaffold는 무조건 content가 들어가야만 합니다. 따라서 content()를 만들어 줘야하는데 람다로 전달을 합니다.
저는 MyApp의 매개변수로 @Composable function을 받도록 구현을 하였고 그 content는 MainContent로 Hello를 출력합니다.
아래와 같이 Title text와 Back, Favorite, Search 아이콘들이 생성 된 것을 볼 수 있습니다.
BottomBar
BottomBar도 선택적 매개변수입니다.
이 경우 화면 하단의 슬롯을 채우는 Composable function이 필요합니다.
BottomAppBar 또는 BottomNavigation과 같은 사전 정의된 Composable을 사용할 수 있지만 이는 실제로 권장 사항일 뿐입니다. 사용자 정의 Composable도 전달할 수 있습니다.
아래 코드는 bottomBar 매개변수에 전달된 BottomAppBar를 구현합니다.
@Composable
fun MyApp(content: @Composable () -> Unit) {
MovieappTheme {
// A surface container using the 'background' color from the theme
Scaffold( topBar = {
TopAppBar(
title = { Text(text = "Title text") },
navigationIcon = {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
},
actions = {
Icon(imageVector = Icons.Default.Favorite, contentDescription = "Favorite")
Icon( imageVector = Icons.Default.Search, contentDescription = "Search")
}
)
}, bottomBar = {
BottomAppBar(
content = {
Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
Icon(imageVector = Icons.Default.Search, contentDescription = "Search")
Text(text = "BottomAppBar!")
}
)
}) {
content()
}
}
}
간단하게 topBar 아래 bottomBar를 선언하고 BottomAppBar안에 content로 Icon과 Text를 넣어줍니다.
앱의 하단에 아이콘과 Text가 생성됨을 확인할 수 있습니다.
FloatingActionButton
선택적 매개변수입니다.
Floating Action 버튼(FAB)은 화면의 기본 작업을 나타냅니다.
이는 또한 floatingActionButtonPosition(Center 또는 End) 및 isFloatingActionButtonDocked(Boolean, FAB가 BottomBar와 중첩되어야 하는지 여부)로 구성할 수 있습니다.
이 슬롯에 대해 기본적으로 구성 가능한 것은 FloatingActionButton이지만 Composable 가능한 모든 기능을 사용할 수 있습니다.
아래 코드는 floatingActionButton 매개변수에 전달된 FloatingActionButton을 구현합니다.
@Composable
fun MyApp(content: @Composable () -> Unit) {
MovieappTheme {
// A surface container using the 'background' color from the theme
Scaffold( topBar = {
TopAppBar(
title = { Text(text = "Title text") },
navigationIcon = {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
},
actions = {
Icon(imageVector = Icons.Default.Favorite, contentDescription = "Favorite")
Icon( imageVector = Icons.Default.Search, contentDescription = "Search")
}
)
}, bottomBar = {
BottomAppBar(
content = {
Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
Icon(imageVector = Icons.Default.Search, contentDescription = "Search")
Text(text = "BottomAppBar!")
}
)
}, floatingActionButton = {
FloatingActionButton(
onClick = {},
content = {
Icon(imageVector = Icons.Default.Favorite, contentDescription = "Favorite")
}
)
}) {
content()
}
}
}
앱 코드가 점점 길어지네요 -_-
floatingActionButton안에 FloatingActionButton을 넣고 onClick은 구현된게 없으니 {}을 넣습니다. 저기에 함수를 추가하면 버튼을 클릭할 때 무언가 동작을 할 수있습니다.
content에 Icon을 넣어서 아래와 같이 Favorite Icon이 하단 오른쪽에 생성된 것을 볼 수 있습니다.
floatingActionButtonPosition이 default가 end여서 오른쪽 끝에 생성이 되었는데
floatingActionButtonPosition = FabPosition.Center로 설정해주면 중앙으로 오게 됩니다.
Drawer
drawer역시 선택적 매개변수입니다.
drawer 시트는 왼쪽(또는 RTL의 경우 오른쪽)에서 끌어올 수 있는 컨텐츠를 나타냅니다.
코루틴 범위에서 scaffoldState.drawerState.open() 함수를 사용하여 drawer를 표시할 수도 있습니다.
여기에 필요한 Lambda는 이미 ColumnScope의 확장 기능이므로 람다에서 사용할 모든 구성요소는 Column(위에서 아래로) 과 같이 배치됩니다.
애니메이션, 배경 스크림 및 스와이프는 모두 Scaffold에서 처리합니다.
아래 코드는 drawContent 매개변수에 전달된 4개의 항목(Icon 1개, Text 3개)을 구현합니다.
@Composable
fun MyApp(content: @Composable () -> Unit) {
MovieappTheme {
// A surface container using the 'background' color from the theme
Scaffold( topBar = {
TopAppBar(
title = { Text(text = "Title text") },
navigationIcon = {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
},
actions = {
Icon(imageVector = Icons.Default.Favorite, contentDescription = "Favorite")
Icon( imageVector = Icons.Default.Search, contentDescription = "Search")
}
)
}, bottomBar = {
BottomAppBar(
content = {
Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
Icon(imageVector = Icons.Default.Search, contentDescription = "Search")
Text(text = "BottomAppBar!")
}
)
}, floatingActionButton = {
FloatingActionButton(
onClick = {},
content = {
Icon(imageVector = Icons.Default.Favorite, contentDescription = "Favorite")
},
)
}, drawerContent = {
Icon(
modifier = Modifier.padding(14.dp),
imageVector = Icons.Default.Person,
contentDescription = "Person"
)
Text(modifier = Modifier.padding(14.dp), text = "James")
Text(modifier = Modifier.padding(14.dp), text = "Sam")
Text(modifier = Modifier.padding(14.dp), text = "Jon")
}) {
content()
}
}
}
drawer를 번역하면 서랍이라는 뜻입니다. 왼쪽으로부터 오른쪽으로 서랍을 끌어오듯이 끌어오면 다음과 같은 화면이 나옵니다.
위에서 작성했던 Person Icon과 Text들이 나오는 것을 볼 수 있습니다.
SnackbarHost
Snackbar는 조금 더 복잡합니다.
Snackbar를 사용자 정의로 구현하고 싶을 때 snackbarHost 매개변수를 사용할 수 있습니다.
그렇지 않으면 SnackbarHost에서 구현된 기본 디자인 및 동작이 사용됩니다.
지금은 SnackbarHost가 작업을 수행하도록 합시다.
Snackbars는 화면 하단에 앱 프로세스에 대한 간단한 메시지를 제공합니다.
예제에서 FAB를 클릭하면 Snackbar가 표시됩니다.
showSnackBar(...)는 일시 중단 함수(suspend function)이므로 코루틴 범위(coroutine scope)가 필요합니다.
우리는 단순히 RememberCoroutineScope()를 사용하여 합성 경계(composition bound)를 얻을 수 있습니다.
SnackbarHost는 한 번에 최대 하나의 snackbar를 표시하도록 보장하며 나머지는 대기열에 있습니다.
rememberCoroutineScope()를 생성하고, rememberScaffoldState()도 생성해줍니다.
아까 배웠던 Scaffold의 매개변수 중 scaffoldState에 생성한 scaffoldState를 넣습니다.
이걸 넣어주지 않으면 FAB를 클릭하여도 snackbar 메시지가 뜨지 않습니다.
아까 공백으로 두었던 onClick에 scope.launch를 하고 showSnackbar를 호출한 후 message를 넣습니다.
@Composable
fun MyApp(content: @Composable () -> Unit) {
MovieappTheme {
val scope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
// A surface container using the 'background' color from the theme
Scaffold( topBar = {
TopAppBar(
title = { Text(text = "Title text") },
navigationIcon = {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
},
actions = {
Icon(imageVector = Icons.Default.Favorite, contentDescription = "Favorite")
Icon( imageVector = Icons.Default.Search, contentDescription = "Search")
}
)
}, bottomBar = {
BottomAppBar(
content = {
Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
Icon(imageVector = Icons.Default.Search, contentDescription = "Search")
Text(text = "BottomAppBar!")
}
)
}, scaffoldState = scaffoldState, floatingActionButton = {
FloatingActionButton(
onClick = {
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(message = "Hello! Snackbar")
}
},
content = {
Icon(imageVector = Icons.Default.Favorite, contentDescription = "Favorite")
},
)
}, drawerContent = {
Icon(
modifier = Modifier.padding(14.dp),
imageVector = Icons.Default.Person,
contentDescription = "Person"
)
Text(modifier = Modifier.padding(14.dp), text = "James")
Text(modifier = Modifier.padding(14.dp), text = "Sam")
Text(modifier = Modifier.padding(14.dp), text = "Jon")
}, floatingActionButtonPosition = FabPosition.Center) {
content()
}
}
}
위의 내용은 좀 더 자세히 추후에 정리하고자 합니다.
FAB 클릭 시 아래와 같이 Hello! Snackbar가 뜨는 것을 볼 수 있습니다.
Content
content: @Composable (PaddingValues) -> Unit
Content는 화면의 남은 영역입니다.
여기에서 어떠한 Composable function도 사용할 수 있습니다.
람다는 위쪽 및 아래쪽 막대를 적절하게 오프셋하기 위해 Modifier.padding을 통해 콘텐츠 루트에 적용해야 하는 PaddingValues를 받습니다.
https://www.goodrequest.com/blog/jcb-scaffold
'Android > Jetpack Compose Concepts' 카테고리의 다른 글
Jetpack Compose 1.1 릴리즈! (0) | 2022.02.11 |
---|---|
JETPACK COMPOSE: OutlinedTextField, FilledTextField에 대해 알아보자 (0) | 2022.01.26 |
JETPACK COMPOSE: Border에 대해 알아보자 (0) | 2022.01.23 |
JETPACK COMPOSE: Jetpack Alpha for Glance Widgets (0) | 2022.01.21 |
JETPACK COMPOSE: Column에 대해 알아보자 (0) | 2022.01.13 |
댓글