추가 + 아래꺼는 기본적으로 paddingValues에 TopAppBar , Status bar, Navigation bar 가 자동적으로 잡힌다 이 내용을 추가해야한다.
Scaffold
안드로이드 앱을 개발하다 보면 상태 바(Status Bar), 네비게이션 바(Navigation Bar), 그리고 키보드(IME) 같은 시스템 UI와의 공존이 꽤 까다롭다. XML에서는 잘 정리되었는데 이번 Compose 개발 중 또 까다롭다 느껴 Compose 관점에서 정리해보겠다.
이 글에서는 WindowCompat.setDecorFitsSystemWindows()와 Compose의 시스템 바/키보드 패딩 처리를 한 번에 정리해본다.
1. 시스템 바란?
안드로이드의 시스템 바(System Bars)는 운영체제가 직접 관리하는 UI 영역이다.
Status Bar (상태 바)
- 위치: 화면 최상단
- 내용: 시간, 배터리, 네트워크 상태 등
- 일반적 높이: 약 24~30dp (기기마다 다름)
Navigation Bar (네비게이션 바)
- 위치: 화면 최하단 또는 측면
- 내용: 뒤로가기, 홈, 최근 앱 버튼 또는 제스처 내비게이션 영역
- 일반적 높이: 약 48dp
2. WindowCompat.setDecorFitsSystemWindows()
이 함수는 “시스템 바 영역에 앱 콘텐츠를 넘어서 할 것인지”를 결정한다.
WindowCompat.setDecorFitsSystemWindows(window, boolean)
- true(기본값)
- 시스템이 자동으로 콘텐츠를 시스템 바 영역 밖에 배치.
- 콘텐츠가 시스템 바에 가려지지 않음.
- 개발자가 시스템 바를 거의 신경 쓰지 않아도 됨.
- 대신 Edge-to-Edge(컨텐츠가 시스템 바 뒤로 가는 효과)는 불가능.
- 예를 들어, 실제 화면 높이가 2400dp이고
- 상태 바: 100dp /네비게이션 바: 48dp 라면 콘텐츠에서 사용할 수 있는 높이는 2252dp가 된다.
- LocalConfiguration.current.screenHeightDp = 2252 와 같이 시스템 바를 제외한 값이 들어온다.
- false(Edge-to-Edge 모드)
- 앱 콘텐츠가 전체 화면(시스템 바 영역 포함)을 사용.
- 시스템 바는 콘텐츠 위에 오버레이.
- 개발자가 직접 패딩을 처리해야 함.
- Edge-to-Edge 배경, 이미지 연출 가능
- 이 경우 LocalConfiguration.current.screenHeightDp는 시스템 바까지 포함한 전체 높이(예: 2400dp)가 된다.
패딩을 안 주면 텍스트/버튼이 상태 바·네비게이션 바 뒤에 가려질 수 있다.
false로 설정해야 imePadding()이 작동하는 이유:
1. setDecorFitsSystemWindows(window, false) - Edge-to-Edge 모드
WindowCompat.setDecorFitsSystemWindows(window, false)
- 앱이 전체 화면에 그려집니다 (status bar, navigation bar, 키보드 영역까지)
- 시스템이 자동으로 패딩을 적용하지 않습니다
- 개발자가 직접 WindowInsets를 처리합니다
- Compose의 Modifier.imePadding(), Modifier.systemBarsPadding() 등이 정상 작동합니다
- WindowInsets를 직접 읽고 제어할 수 있습니다
2. setDecorFitsSystemWindows(window, true) - 레거시 모드 (현재 코드)
WindowCompat.setDecorFitsSystemWindows(window, true) // 현재 설정
- 시스템이 자동으로 시스템 UI 영역을 피해서 컨텐츠를 배치합니다
- 앱의 루트 뷰에 자동으로 패딩이 적용됩니다
- 개발자가 WindowInsets를 직접 제어할 수 없습니다
- Compose의 imePadding()이 제대로 작동하지 않습니다 (이미 시스템이 처리했기 때문)
왜 false가 필요한가?
Compose의 imePadding()은 WindowInsets API를 사용합니다. true로 설정하면:
- WindowInsets가 루트 뷰에서 소비됩니다
- Compose가 insets 정보를 받지 못합니다
- imePadding()이 0dp를 반환합니다
false로 설정하면:
- WindowInsets가 앱 전체에 전달됩니다
- Compose가 insets 정보를 정확히 받습니다
- imePadding()이 키보드 높이만큼 패딩을 적용합니다
3. Compose의 시스템 바/키보드 패딩 Modifier
Edge-to-Edge(decorFitsSystemWindows = false)를 쓸 때 필수적으로 사용하는 Modifier들입니다.
3.1 statusBarsPadding()
Box(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding() // 상태 바 높이만큼 상단 패딩
) {
// 컨텐츠
}
- 상단에 상태 바 높이만큼 패딩을 추가.
- 상단 컨텐츠가 상태 바에 가려지지 않도록 보호.
3.2 navigationBarsPadding()
Column(
modifier = Modifier
.fillMaxSize()
.navigationBarsPadding() // 네비게이션 바 높이만큼 하단 패딩
) {
// 컨텐츠
}
- 하단에 네비게이션 바 높이만큼 패딩을 추가.
- 하단 버튼/텍스트가 네비게이션 바에 가려지지 않도록 보호.
3.3 systemBarPadding()
Box(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding() // 상태 바 + 네비게이션 바
) {
// 컨텐츠
}
- statusBarsPadding() + navigationBarsPadding() 조합과 동일.
- 상·하단 시스템 바 영역을 한 번에 회피.
3.4 imePadding() (키보드 패딩)
Column(
modifier = Modifier
.fillMaxSize()
.imePadding() // 키보드가 올라오면 그 높이만큼 하단 패딩
) {
// 컨텐츠
}
- 키보드(IME)가 올라올 때, 하단에 키보드 높이만큼 패딩 추가.
- 입력 필드가 키보드에 가려지지 않도록 보호.
- 중요: setDecorFitsSystemWindows(window, false)일 때에만 정상 동작.
4. 화면 크기 측정 방법
4.1 LocalConfiguration.current.screenHeightDp
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
- decorFitsSystemWindows(true): 시스템 바 제외 높이.
- decorFitsSystemWindows(false): 시스템 바 포함 전체 높이.
- 컴포지션 시점의 정적 값이라, 실제 레이아웃 결과와 다를 수 있음.
4.2 onGloballyPositioned { }
var measuredHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
Box(
modifier = Modifier
.fillMaxSize()
.onGloballyPositioned { coordinates ->
with(density) {
measuredHeight = coordinates.size.height.toDp()
}
}
) {
// 컨텐츠
}
- 해당 컴포저블이 레이아웃된 실제 크기를 측정.
- 패딩, 마진까지 모두 반영된 값.
- 초기에는 0일 수 있고, 레이아웃 후 리컴포지션되며 값이 세팅됨.
- 복잡한 레이아웃 계산에서는 이 값을 기준으로 계산하는 것이 안전.
5. 시나리오로 보는 동작 차이
여기서는 다음을 가정합니다.
- 전체 화면 높이: 2400dp
- 상태 바: 100dp
- 네비게이션 바: 48dp
5.1 기본 설정 (DecorFits = true)
- LocalConfiguration.screenHeightDp = 2252
- Box.fillMaxSize() 내부 Column 높이 = 2252dp
- 시스템이 시스템 바 영역을 알아서 제외해주므로, 별도의 패딩 처리 없이도 UI가 깨지지 않습니다.
5.2 Edge-to-Edge + 패딩 없음 (문제 사례)
- WindowCompat.setDecorFitsSystemWindows(window, false)
- LocalConfiguration.screenHeightDp = 2400
- Box, Column 모두 2400dp로 레이아웃
- 상태 바/네비게이션 바가 콘텐츠 위에 오버레이 → 텍스트가 가려질 수 있음.
5.3 Edge-to-Edge + statusBarsPadding() 만 사용
- 상단은 상태 바 높이만큼 패딩이 생겨서 콘텐츠가 잘 보임.
- 하단은 여전히 네비게이션 바에 가려질 수 있음.
- 전체 화면 높이는 여전히 2400dp로 인식.
5.4 잘못된 높이 계산 예시
Box에 statusBarsPadding()을 주고, screenHeight - headerHeight로만 계산하는 경우:
- screenHeight는 전체(2400dp)
- Box 내부 실제 사용 가능 높이는 2400 - 100 = 2300dp
- 헤더가 300dp라면 실제 사용 가능 영역은 2300 - 300 = 2000dp
- 그런데 2400 - 300 = 2100dp로 계산하면 100dp가 넘치면서 하단이 시스템 바 영역을 침범하게 됩니다.
5.5 올바른 해결 방법
Box의 실제 측정 높이를 기준으로 계산합니다.
var boxHeight by remember { mutableStateOf(0.dp) }
var headerHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
Box(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.onGloballyPositioned { coordinates ->
with(density) {
boxHeight = coordinates.size.height.toDp()
}
}
) {
Column(
modifier = Modifier
.onGloballyPositioned { coordinates ->
with(density) {
headerHeight = coordinates.size.height.toDp()
}
}
) {
Text("헤더 영역")
}
Column(
modifier = Modifier
.align(Alignment.BottomCenter)
.height(boxHeight - headerHeight)
) {
Text("컨텐츠 영역")
}
}
- Box 실제 높이(예: 2300dp)에서 헤더 높이(300dp)를 빼서, 정확히 2000dp 영역만 하단 콘텐츠에 할당.
- 시스템 바, 패딩까지 고려된 실제 영역 기준이라 어긋나지 않습니다.
6. 키보드(IME)와의 관계
6.1 adjustNothing (기본값)
<activity
android:name=".MainActivity"
android:windowSoftInputMode="adjustNothing" />
- 키보드가 올라와도 Window 크기 변화 없음.
- 키보드가 앱 콘텐츠 위에 그대로 오버레이.
- 이 모드에서는 .imePadding()이 제대로 동작하지 않습니다.
6.2 setDecorFitsSystemWindows(false) + .imePadding()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
}
@Composable
fun PasswordFormScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
) {
TextField(...)
}
}
Edge-to-Edge 모드에서 .imePadding()을 사용하면, 키보드가 올라올 때 하단 패딩이 자동으로 늘어나면서 TextField가 키보드 위로 올라옵니다.
6.3 adjustResize vs decorFits=false + imePadding
| 방식 | Window 크기 변화 | 제어 주체 | 특징 |
| android:windowSoftInputMode="adjustResize" | 키보드 높이만큼 축소 | 시스템 자동 | 전체 레이아웃에 일괄 적용 |
| setDecorFits(false) + .imePadding() | 변화 없음 | 개발자(Modifier) | 원하는 컴포넌트에만 선택적 적용 |
Compose에서는 후자가 더 유연하고, 화면별로 다른 동작을 주기 좋습니다.
7. Best Practices 정리
7.1 Modern Android (Edge-to-Edge)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
MyApp()
}
}
}
@Composable
fun MyApp() {
Scaffold(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
) { paddingValues ->
// 컨텐츠
}
}
7.2 입력 폼 화면
@Composable
fun FormScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.imePadding()
) {
TextField(...)
TextField(...)
}
}
7.3 복잡한 레이아웃 계산
@Composable
fun ComplexLayoutScreen() {
var containerHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
Box(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.onGloballyPositioned { coordinates ->
with(density) {
containerHeight = coordinates.size.height.toDp()
}
}
) {
// containerHeight 기반으로 서브 레이아웃 계산
}
}
7.4 화면별 다른 설정 예시
@Composable
fun NormalScreen() {
Box(modifier = Modifier.systemBarsPadding()) {
// 컨텐츠
}
}
@Composable
fun FormScreen() {
Box(
modifier = Modifier
.systemBarsPadding()
.imePadding()
) {
// 컨텐츠
}
}
@Composable
fun FullscreenScreen() {
Box(modifier = Modifier.fillMaxSize()) {
// 배경 (시스템 바 뒤까지 확장)
Image(...)
// 실제 컨텐츠 (시스템 바 피하기)
Column(modifier = Modifier.systemBarsPadding()) {
Text("컨텐츠")
}
}
}
요약 표
| 설정 | screenHeightDp | 실제 Box 크기 | 특징 |
| DecorFits = true | 시스템 바 제외 높이 | screenHeightDp와 동일 | 시스템 자동 관리, 패딩 불필요 |
| DecorFits = false | 전체 화면 높이 | screenHeightDp와 동일 | Edge-to-Edge, 패딩 수동 처리 필수 |
| DecorFits = false + statusBarsPadding() | 여전히 전체 화면 높이 | screenHeightDp - 상태 바 높이 | 실제 측정값 필요 (Box 기준 계산 권장) |
핵심 포인트
- setDecorFitsSystemWindows(false)를 쓰면 시스템 바/키보드에 대한 패딩 Modifier 사용이 사실상 필수입니다.
- 복잡한 레이아웃 계산에서는 LocalConfiguration 대신 onGloballyPositioned()로 실제 측정값을 기준으로 계산해야 합니다.
- .imePadding()은 decorFitsSystemWindows(false) 환경에서 가장 잘 동작하며, adjustResize보다 더 세밀한 제어가 가능합니다.
- 화면의 목적(일반 화면, 입력 폼, 풀스크린 배경 등)에 따라 systemBarsPadding, statusBarsPadding, navigationBarsPadding, imePadding을 적절히 조합하는 것이 중요합니다.
'Android > Compose' 카테고리의 다른 글
| Compose에서 margin이 사라진 이유 (0) | 2026.01.02 |
|---|---|
| Compose에서 ConstraintLayout이 사라진 이유 (0) | 2026.01.02 |
| Compose UI 컴포넌트 설계 (0) | 2024.12.13 |
| XML 에서 Copose로 전환하면 좋은 이유 (0) | 2024.12.13 |