(2022.09.12 기준)
Accompanist Inset 라이브러리는 Deprecated 되었습니다. androidx.compose.foundation에서 관련 기능을 사용해보세요.
안녕하세요. 점냥입니다:)
Android에서는 Status Bar, Navigation Bar 등등의 다양한 시스템 UI가 존재합니다. Android 최상위 뷰는 기본적으로 시스템 UI와 겹치지 않도록 InSets Padding이 적용되어 있습니다. 하지만 상태바처럼 시스템 UI 위치에 뷰를 배치하고 경우가 종종 있는 데요. Compose에서는 이를 어떻게 구현할 수 있을 까요?
Accompanist Inset
Accompanist는 Google에서 Compose에서 사용 가능한 여러 실험적인 API들을 무료로 제공해주는 그룹입니다. Inset 라이브러리는 Compose 특성에 맞게 WindowInsets 속성을 쉽게 적용할 수 있도록 도와줍니다.
Step 1. Activity DecorView 확장하기
WindowCompat.setDecorFitsSystemWindows(window, false)
Inset 라이브러리를 사용하기 전 필수적으로 Activity에서 위 코드를 호출해야 합니다. 이는 DecorView에 기본적으로 설정되고 있던 Window Inset Padding을 무시합니다. 이는 Inset 라이브러리를 이용하여 시스템 UI Padding을 직접 적용하고 제어하고자 하기 때문입니다.
결과물을 확인해보면 Bottom Navigation Bar 영역까지는 올바르게 확장된 것을 볼 수 있지만, 상단 Status Bar 영역은 이전과 그대로인 것을 확인할 수 있습니다. Status Bar 영역까지 확장해주기 위해서는 Status Bar Color를 투명으로 변경해주어야 합니다!
<item name="android:statusBarColor">@android:color/transparent</item>
Step 2. ProvideWindowInsets
@Composable
fun ProvideWindowInsets(
consumeWindowInsets: Boolean = true,
windowInsetsAnimationsEnabled: Boolean = true,
content: @Composable () -> Unit
) {
val view = LocalView.current
val windowInsets = remember { RootWindowInsets() } --- (1)
DisposableEffect(view) { --- (2)
val observer = ViewWindowInsetObserver(view)
observer.observeInto(
windowInsets = windowInsets,
consumeWindowInsets = consumeWindowInsets,
windowInsetsAnimationsEnabled = windowInsetsAnimationsEnabled
)
onDispose { observer.stop() }
}
CompositionLocalProvider(LocalWindowInsets provides windowInsets) { --- (3)
content()
}
}
Accompanist Inset 라이브러리에서는 Slot API 패턴으로 구현된 ProvideWindowInsets Composable 함수를 제공합니다. System UI의 정보들은 Compose 외부인 뷰에서 접근이 가능하기 때문에 DisposableEffect를 사용해 Compose에서 접근 가능한 State로 변경해주는 작업을 진행해야 합니다.
(1)은 remeber를 사용하여 Window Inset State를 선언합니다. RootWindowInsets()은 실질적으로 View에서 Window Inset 정보를 가져오고 변환하는 코드와 Composable에서 접근할 Type 등이 자세히 선언되어 있습니다.
(2)은 DisposableEffect를 이용해 Composable 수명 주기 내에서 끊임없이 Window Inset의 변화를 감지하고 해당 값을 windowInset State에 경신합니다.
(3)은 Window Inset State를 매번 매개변수로 전달해주지 않고 CompositionLocal로 제공해줍니다. 그로 인해 content 루트 Composable부터 자식 Composable까지 로컬 변수처럼 쉽게 접근할 수 있습니다.
Step 3. WindowInset 접근
사진처럼 IME(키보드), Status Bar 등등 Window Inset 정보를 쉽게 접근할 수 있습니다. 이것을 어떻게 활용할 수 있는지 짧게 몇 개의 Case를 살펴봅시다.
Case 1. Window Inset 사이즈 값 확인
@Composable
fun ImeAvoidingBox() {
val insets = LocalWindowInsets.current
val imeBottom = with(LocalDensity.current) { insets.ime.bottom.toDp() }
Box(Modifier.padding(bottom = imeBottom))
}
inset.ime.bottom.toDp()를 이용해 키보드 높이를 가져오는 코드입니다. 이처럼 Status Bar, Bottom Navigation Bar의 높이를 가져올 수 있습니다.
Case 2. Window Inset을 이용한 몇 가지 Modifier 함수들
inline fun Modifier.statusBarsPadding(): Modifier = composed {
padding(
rememberInsetsPaddingValues(
insets = LocalWindowInsets.current.statusBars,
applyTop = true
)
)
}
Case 1을 활용하여 Status Bar 높이 값을 가져와 Top Padding 값으로 넣어주는 코드입니다.
마무리
위에서 올바르게 확장을 해주었지만 Status Bar와 Bottom Navigation Bar 영역에서 UI가 겹치는 부분을 문제가 있었습니다. 현재 우리는 시스템 UI 영역 내에 특별히 그려야 할 이유가 없기 때문에 겹치는 영역에 존재하는 System UI의 크기만큼 패딩을 주면 됩니다.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NiaApp() {
NiaTheme {
ProvideWindowInsets {
...
Scaffold(
...
) { padding ->
Surface(Modifier.fillMaxWidth()) {
NiaNavGraph(
...
modifier = Modifier
.systemBarsPadding(bottom = false)
.padding(padding)
)
}
}
}
}
}
systemBarsPadding() Modifier 함수는 Status Bar와 Bottom Navigation Bar 둘 다 포함하는 Inset입니다. 내부 변수인 top에는 Status Bar의 높이 값이 bottom에는 Bottom Navigation Bar 높이 값을 가지고 있습니다. 코드 상에서는 bottom = false 값을 주어 상태바의 높이 값만큼 Top Padding을 주었습니다.
@Composable
private fun NiaBottomBar(
navigationActions: NiaNavigationActions,
currentRoute: String
) {
Surface(color = MaterialTheme.colorScheme.surface) {
NavigationBar(
modifier = Modifier.systemBarsPadding(top = false),
tonalElevation = 0.dp
) {
...
}
}
}
위와 비슷하게 이번에는 systemBarsPadding 값에 top = false 값을 주어 Bottom Navigation Bar의 값만큼 Bottom Padding을 준 코드입니다.
이외 자세한 설명은 Guide - Accompanist (google.github.io) 공식 문서를 참고하면 좋을 것 같습니다!
Google android opensource인 nowinandroid 프로젝트를 클론 코딩하면서 새롭게 알게 된 내용을 정리 중입니다.
깃허브 링크를 확인해 주세요
'Android > Compose' 카테고리의 다른 글
[Android] Compose BackdropScaffold 적용해보기 (0) | 2023.01.19 |
---|---|
[Android] Compose PreviewParameterProvider (2) | 2022.09.22 |
[Android] Bottom Navigation Bar UI with Compose - (1) (0) | 2022.06.06 |
[Android] Jetpack Compose란? (0) | 2022.05.22 |
[Android] Compose Text 양끝으로 정렬하는 여러 방법 (0) | 2021.06.03 |