Android 15 edge-to-edge, Scaffold(), and topBar issue

맨 꼭대기 Scaffold() 에 topBar 가 있으면 edge-to-edge 설정 시 문제가 생겼고, Inset 을 Scaffold() 안에 적용하면 좋은 점이 있었다. Dark 테마에서 Status bar 내의 글자, 아이콘 색깔 문제를 해결했고, Navigation bar 를 투명하게 만들었다.

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

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 설정

Navigation bar 투명하게

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

Leave a Comment