맨 꼭대기 Scaffold() 에 topBar 가 있으면 edge-to-edge 설정 시 문제가 생겼고, Inset 을 Scaffold() 안에 적용하면 좋은 점이 있었다. Dark 테마에서 Status bar 내의 글자, 아이콘 색깔 문제를 해결했고, Navigation bar 를 투명하게 만들었다.
Contents
edge-to-edge 가 강제 적용되는 Android 15
여차저차한 이유로
targetSdk = 35
로 설정하게 되었다. 그런데 이 Android 15 에서는 edge-to-edge 가 강제 적용 사항이라고 한다. 새 앱 모듈을 생성해 보면
enableEdgeToEdge()
함수가 기본 호출된 것을 볼 수 있다. 앱의 배경색이 Status bar 와 Navigation bar 에 똑같이 적용되어 있다. 새로 생성된 앱은 Scaffold() 를 사용하고 있는데 겉보기에 별 문제는 없어 보인다. 앱 내용이 Status bar 로 올라가지 않았다.
기존 앱에 edge-to-edge 를 적용하면 문제 발생
새로 생성한 앱 모듈과 달리 기존 앱에 enableEdgeToEdge() 를 실행해 보면 앱이 상태바까지 치고 올라가더라.
개발 문서를 참고해서 Inset 을 기존 앱에 적용해 보았다[맨 아래의 참고 문서]. 그런데 내 바람대로 잘 되지 않았고 다음과 같은 문제점으로 인해 삽질을 좀 했다:
- 상태바의 배경색이 앱의 배경색을 따르지 않는 문제
- 내비게이션 바가 불투명하게 되는 문제
설정 목표
- 앱의 내용이 상태바를 침범하지 않게 하기
- 상태바의 배경색이 앱의 배경색을 따르게 하기
- 상태바 내의 글자 색, 아이콘 색이 Light / Dark 테마 변동에 따라 반전되게 하기
- 내비게이션 바를 투명하게 만들기
시행착오 끝에 얻은 해답
일단 Scaffold() 에 topBar 가 있을 때 다음의 문제가 생겼다:
맨 꼭대기 Scaffold() 에 topBar 가 있으면 edge-to-edge 설정 시 상태바의 색깔이 앱과 연동되지 않는 문제가 생김
사실 맨 꼭대기 Scaffold() 에 topBar 를 넣을 이유가 별로 없더라. 그래서 topBar 에 넣었던 내용을 Scaffold() 본문 안으로 옮겨버렸다.
참고로, Navigation destination 으로 쓰이는 컴포저블의 Scaffold() 에는 topBar 가 있어도 상태바의 배경색이 앱의 배경색을 따르게 할 수 있었다. 이때는 Scaffold() 바깥에 Inset 을 적용하기도 했다.
그후 내비게이션 바가 불투명하게 되는 문제로 한참 헤맸다. 개발 문서에 보면 Scaffold 바깥에 Inset 을 적용한 예가 있는데 나한테는 맞지 않는 방법이었다. 나는 Inset 을 Scaffold 본문 안에 적용했다. 다음 코드와 같이 구성해서 edge-to-edge 관련 상태바 배경색 문제를 해결할 수 있었다.
Scaffold(
modifier = Modifier
.imePadding()
.fillMaxSize(),
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize(),
) {
Spacer(Modifier.windowInsetsTopHeight(WindowInsets.statusBars))
Column(
modifier = Modifier
// .padding(paddingValues),
.consumeWindowInsets(paddingValues),
) {
AppContents()
}
}
}
위의 코드에서 Column() 함수를 중첩해서 사용했다.
Navigation destination 으로 쓰이는 Composable 에는 대략 다음과 같이 해두면 큰 문제는 없었다.
Column(
modifier = Modifier
.fillMaxSize(),
) {
Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))
Scaffold(
modifier = Modifier
.fillMaxSize(),
topBar = {
// Composable here
}
) { paddingValues ->
// 필요하면 이런 코드도 넣어준다.
// Spacer(
// Modifier
// .padding(20.dp)
// .consumeWindowInsets(PaddingValues(20.dp))
// )
Column(
modifier = Modifier
// .padding(paddingValues),
.consumeWindowInsets(paddingValues),
) {
// Contents here
}
}
}
MainActivity 설정
MainActivity 에도 몇 가지 설정을 해주어야 했다.
안드로이드 10 이상에서는 다음과 같이 설정하여 내비게이션바를 투명하게 만든다:
(참고: https://developer.android.com/codelabs/edge-to-edge#2)
window.isNavigationBarContrastEnforced = false
Status bar 내의 글자, 아이콘 색깔
그 외에, 한 가지 문제가 더 있었다. Dark 테마에서 상태바 내의 글자, 아이콘 색깔이 어두웠다. 밝은 색이어야 하는데.
내 앱에서는 앱의 Light / Dark 설정이 시스템 설정을 따를지, 아니면 강제 설정을 따를지를 사용자가 결정할 수 있다. 그래서 다음과 같이 Proto DataStore 에서 Light / Dark 설정 값을 읽어와서 그에 따라 글자와 아이콘 색깔이 그 반대가 되도록 했다:
val preferences by viewModel.preferences.collectAsStateWithLifecycle()
// 상태바 내의 글자, 아이콘 색깔이 앱 테마 설정, 특히 Light/Dark 설정과 연동되게 함
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = !when (preferences.selectedButtonIndex1) {
0 -> isSystemInDarkTheme() // System 따라감
2 -> true // 강제로 Dark
else -> false // 나머지는 Light
}
}
위의 코드는 .collectAsStateWithLifecycle() 함수때문에 Composable 안에서 사용해야 했다.
만약에 사용자가 직접 Light / Dark 테마를 선택할 수 없고, 그냥 시스템 설정을 따르도록 구성돼 있다면 다음과 같이 하면 될 것이다(실행해 보지는 않음):
// 상태바 내의 글자, 아이콘 색깔이 앱 테마 설정, 특히 Light/Dark 설정과 연동되게 함
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = !isSystemInDarkTheme()
}
종합하여, MainActivity 가 다음과 같이 되도록 했다:
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()
enableEdgeToEdge()
// WindowCompat.setDecorFitsSystemWindows(window, false)
// remove default three-button navigation background protection
// https://developer.android.com/codelabs/edge-to-edge#2
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
setContent {
val preferences by viewModel.preferences.collectAsStateWithLifecycle()
// 상태바 내의 글자, 아이콘 색깔이 앱 테마 설정, 특히 Light/Dark 설정과 연동되게 함
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = !when (preferences.selectedButtonIndex1) {
0 -> isSystemInDarkTheme() // System 따라감
2 -> true // 강제로 Dark
else -> false // 나머지는 Light
}
}
AppTheme {
//MainScreen() 또는 AppNavHost()
}
}
}
}
참고 문서
https://developer.android.com/develop/ui/compose/layouts/insets
https://developer.android.com/about/versions/15/behavior-changes-15#edge-to-edge