이전 scaffold에서 설명하지 않았던 Snackbar에 대해 알아보자!
Compose에서 Snackbar는 SnackbarHost를 통해 쉽게 사용할 수 있다.
(SnackbarHost를 통하지 않으면 Snackbar의 상태와 꺼지는 시간을 직접 구현해야 할 텐데 정말 필요할 경우가 아니면 굳이..?)
이 SnacbarHost는 Scaffold에서 제공하므로 Scaffold를 사용하면 쉽게 사용할 수 있다.
그러나 Scaffold를 사용하지 않을 수도 있기 때문에 Scaffold에서 사용했을 때와 아닐 때를 나눠서 설명을 하겠다.
우선 SnackbarHost에 대해 알아보자!
[Scaffold]에서 [Snackbar]를 사용하여 항목을 적절히 표시, 숨기기 및 취소할 호스트
기본 매개 변수가 포함된 이 구성 요소는 [Scaffold]에 내장되어 있습니다.
기본값으로 [Snackbar], 그리고 [ScaffoldState.snackbarHostState]]와 [스낵바 Host State.showSnackbar]를 사용한다.
SnackbarHost.kt를 봐보면 위와 같이 설명이 되어있다.
Scaffold를 사용할 때 쓸 수 있는 기능이라고 볼 수 있을 것 같다.
@Composable
fun SnackbarHost(
hostState: SnackbarHostState,
modifier: Modifier = Modifier,
snackbar: @Composable (SnackbarData) -> Unit = { Snackbar(it) }
) {
val currentSnackbarData = hostState.currentSnackbarData
val accessibilityManager = LocalAccessibilityManager.current
LaunchedEffect(currentSnackbarData) {
if (currentSnackbarData != null) {
val duration = currentSnackbarData.duration.toMillis(
currentSnackbarData.actionLabel != null,
accessibilityManager
)
delay(duration)
currentSnackbarData.dismiss()
}
}
FadeInFadeOutWithScale(
current = hostState.currentSnackbarData,
modifier = modifier,
content = snackbar
)
}
내부 코드를 보면, SnackbarHostState, modifier, SnackbarData를 파라미터로 받고 있다.
그리고 SnackbarData에서 지정한 값에 따라 delay해주고 dismiss를 해주고 있다.
띄웠다가 시간이 지나면 꺼지는 것을 신경쓰지 않도록 편하게 구현이 되어있다!
그렇다면 SnacbarHostState 는 어떤 구조로 되어 있을까?
@Stable
class SnackbarHostState {
/**
* Only one [Snackbar] can be shown at a time.
* Since a suspending Mutex is a fair queue, this manages our message queue
* and we don't have to maintain one.
*/
private val mutex = Mutex()
/**
* The current [SnackbarData] being shown by the [SnackbarHost], of `null` if none.
*/
var currentSnackbarData by mutableStateOf<SnackbarData?>(null)
private set
/**
중간에 긴 설명은 생략 .....
*/
suspend fun showSnackbar(
message: String,
actionLabel: String? = null,
duration: SnackbarDuration = SnackbarDuration.Short
): SnackbarResult = mutex.withLock {
try {
return suspendCancellableCoroutine { continuation ->
currentSnackbarData = SnackbarDataImpl(message, actionLabel, duration, continuation)
}
} finally {
currentSnackbarData = null
}
}
@Stable
private class SnackbarDataImpl(
override val message: String,
override val actionLabel: String?,
override val duration: SnackbarDuration,
private val continuation: CancellableContinuation<SnackbarResult>
) : SnackbarData {
override fun performAction() {
if (continuation.isActive) continuation.resume(SnackbarResult.ActionPerformed)
}
override fun dismiss() {
if (continuation.isActive) continuation.resume(SnackbarResult.Dismissed)
}
}
}
SnackbarData의 구현체인 SnackbarDatatImpl과 showSnackbar로 되어 있다.
SnackbarData는 아래 코드를 참고해주면 된다 😄
interface SnackbarData {
val message: String
val actionLabel: String?
val duration: SnackbarDuration
/**
* Function to be called when Snackbar action has been performed to notify the listeners
*/
fun performAction()
/**
* Function to be called when Snackbar is dismissed either by timeout or by the user
*/
fun dismiss()
}
enum class SnackbarDuration {
/**
* Show the Snackbar for a short period of time
*/
Short,
/**
* Show the Snackbar for a long period of time
*/
Long,
/**
* Show the Snackbar indefinitely until explicitly dismissed or action is clicked
*/
Indefinite
}
그렇다면 이제 실제 코드를 통해 어떻게 구현하는지를 알아보자!
1. Scaffold 사용
val cScope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState=scaffoldState) {
Button(onClick = {
cScope.launch {
val result =
scaffoldState.snackbarHostState.showSnackbar(
"스낵바 😁😁😁",
"닫기",
SnackbarDuration.Short)
when(result) {
SnackbarResult.ActionPerformed -> {}
SnackbarResult.Dismissed -> {}
}
}
}){
Text("스낵바 보이기")
}
}
Scaffold에 remeberScaffoldState를 넣어주고,
scaffoldState를 통해서 snackbarHostState를 통해 showSnackbar를 호출해
버튼을 클릭하면 스택바가 보이도록 해주었다.
결과물은 왼쪽처럼 나오게 된다.
showSnackbar는 위의 SnackbarHostState의 코드를 보면 SnackbarResult를 반환하기 때문에 result를 받아서 동작에 따라 행동을 처리할 수가 있다.
그리고 showSnackbar를 호출할 때 coroutineScope를 사용해서 처리하는데
showSnackbar 자체가 suspend fun이기 때문이다.
coroutineScope 내에서만 처리할 수 있다 😁
2. Scaffold 미사용
val cScope = rememberCoroutineScope()
val snackBarHost = remember { SnackbarHostState() }
Box(modifier = Modifier.fillMaxSize()) {
Button(onClick = {
cScope.launch {
val snackBar =
snackBarHost.showSnackbar(
"스낵바 😁😁😁",
"닫기",
SnackbarDuration.Short)
// result
when(snackBar) {
SnackbarResult.ActionPerformed -> {}
SnackbarResult.Dismissed -> {}
}
}
}){
Text("스낵바 보이기")
}
SnackbarHost(hostState = snackBarHost)
}
동일하게 SnackbarHostState를 사용하여 만들어주면 된다!
SnackbarHost를 안에 넣어주면 되는데 실행하게 되면 아래와 같이 나오게 된다.
Box에 넣었기 때문에 위치가 저렇게 나오게 되고 Alignment를 사용하여 위치를 조정해서 사용할 수 있다 😄
ex) SnackbarHost( hostState = snackbarHostState, modifier = Modifier.align(Alignment.BottomCenter) )
당연히 Box만 가능한 것은 아니고, Column에 넣게 되면 하단과 같이 보이게 된다.
SnackBarHost를 Button보다 위에 올리게 되면?
버튼이 밀리면서 위에 뜨게 된다 !!!
++ 추가
Snackbar는 구현할 수 있는데, 이제 Toast는 어떻게 구현하면 좋을까?
@Composable
fun ToastGreetingButton(greeting: String) {
val context = LocalContext.current
Button(onClick = {
Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
}) {
Text("Greet")
}
}
CompositionLocal 클래스 (Compose에서 Context, Configuration 또는 View 같은 Android 프레임워크 유형의 값을 전파하는 데 사용)를 사용하고 이 클래스는 Compose 코드가 해당 LocalContext, LocalConfiguration 또는 LocalView와 함께 호스팅된다.
current 속성을 사용해 현재 값에 액세스하게 된다.
LocalContext.current를 제공하여 토스트 메시지를 띄우게 된다!
참고 및 출처
'개발 공부 > 안드로이드' 카테고리의 다른 글
[Compose] 컴포즈 공부하기 9 - TextField (0) | 2023.09.20 |
---|---|
[Compose] 컴포즈 공부하기 8 - CheckBox (0) | 2023.09.13 |
[Compose] 컴포즈 공부하기 6 - Scaffold (0) | 2023.09.07 |
[Compose] 컴포즈 공부하기5 - ConstraintLayout 2 (0) | 2023.09.06 |
[Compose] 컴포즈 공부하기5 - ConstraintLayout 1 (0) | 2023.09.06 |