Scaffold란?
Snackbar을 원하는대로 만들기 위해서는 Scaffold Layout을 알아야 한다. Scaffold란 Material Component들을 편하게 사용할 수 있도록 하기 위해 미리 디자인된 레이아웃이다. Snackbar도 Material Component이므로 Compose에서 Snackbar을 기존 Snackbar의 동작대로 이용하기 위해서는 Scaffold State로 감싸야 한다. 만약 Scaffold로 감싸지 않으면 보통의 Composable과 똑같이 동작한다.
Snackbar 만들기
Snackbar을 만들기 위한 Composable을 먼저 정의해보자. 우리의 목표는 TextField와 Button을 두고 Button을 눌렀을 때 TextField 속의 Text가 Snackbar에 보이는 것이다. 그림1이 우리가 목표하는 결과물이다.
위의 동작을 하는 Composable을 만들기 위해서는 Scaffold를 사용해야 한다. 우리는 Column을 Scaffold로 감싸고 Column내부의 버튼을 눌렀을 때 TextField를 참조해서 snackbar을 생성하는 방식으로 위의 결과물을 구현할 것이다.
1. Scaffold를 사용하기 위해서는 ScaffoldState가 필요하므로 rememberScaffoldState()를 이용해 scaffoldState를 만든다.
val scaffoldState = rememberScaffoldState()
2. textField의 State를 저장하고 Recompose할 때 재구성해야 하므로 MutableState<String>를 만들고 remember로 감싼다.
var textState by remember {
mutableStateOf("")
}
3. 세로로 정렬해야 하므로 Column을 만들고 Column 내부의 컴포넌트가 Snackbar을 만들어야 하므로 Scaffold 레이아웃으로 감싼다. Scaffold에는 파라미터로 위 1번에서 만든 scaffoldState를 넘긴다. Column
Scaffold(
modifier = Modifier.fillMaxSize(),
scaffoldState = scaffoldState
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(12.dp)
) {
}
}
4. Column에 TextField를 배치하고 TextField의 State가 2번의 textField에 저장되도록 설정한다.
Scaffold(
modifier = Modifier.fillMaxSize(),
scaffoldState = scaffoldState
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(12.dp)
) {
TextField(value = textState, onValueChange = { textValue -> textState = textValue })
}
}
5. Spacer을 이용해 여유공간을 만들고 그 뒤에 버튼을 배치한다. 버튼은 TextField의 State를 가져와 Snackbar을 생성하는 onClick 이벤트 리스너를 가진다.
내부를 보면 scaffoldState의 snackbarHostState를 이용해 snackbar을 만들어내는 것을 볼 수 있다. 여기서 추가적으로 이 CoroutineScope 내부에서 snackbar가 불려야 한다는 점을 알아두자. showSnackbar은 suspend fun이고, UI 요소들은 Activity의 CoroutineScope 내부에서만 불려야만 메모리를 낭비하지 않기 때문이다.
Scaffold(
modifier = Modifier.fillMaxSize(),
scaffoldState = scaffoldState
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(12.dp)
) {
TextField(value = textState, onValueChange = { textValue -> textState = textValue })
Spacer(modifier = Modifier.size(12.dp))
Button(onClick = {
lifecycleScope.launch { // 이것이 불리는 CoroutineScope
//scaffoldState를 이용해 snackbar을 만들어냄
scaffoldState.snackbarHostState.showSnackbar("textState : ${textState}")
}
}) {
Text(text = "Show Snackbar")
}
}
}
완성된 코드
완성된 코드는 다음과 같다.
@Composable
fun KotlinWorldSnackbar(lifecycleScope: CoroutineScope) {
val scaffoldState = rememberScaffoldState()
var textState by remember {
mutableStateOf("")
}
Scaffold(
modifier = Modifier.fillMaxSize(),
scaffoldState = scaffoldState
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(12.dp)
) {
TextField(value = textState, onValueChange = { textValue -> textState = textValue })
Spacer(modifier = Modifier.size(12.dp))
Button(onClick = {
lifecycleScope.launch {
scaffoldState.snackbarHostState.showSnackbar("textState : ${textState}")
}
}) {
Text(text = "Show Snackbar")
}
}
}
}
Snackbar 작동 영상
위 코드를 MainActivity에 넣어 실행해보면 다음과 같은 결과물을 얻을 수 있다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KotlinWorldSnackbar(lifecycleScope = lifecycleScope)
}
}
}