본문 바로가기
Android/Compose

Android Compose에서 시스템 바와 화면 크기 측정

by 개발자 인민군 2026. 1. 9.

추가 + 아래꺼는 기본적으로 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 기준 계산 권장)

핵심 포인트

  1. setDecorFitsSystemWindows(false)를 쓰면 시스템 바/키보드에 대한 패딩 Modifier 사용이 사실상 필수입니다.
  2. 복잡한 레이아웃 계산에서는 LocalConfiguration 대신 onGloballyPositioned()로 실제 측정값을 기준으로 계산해야 합니다.
  3. .imePadding()은 decorFitsSystemWindows(false) 환경에서 가장 잘 동작하며, adjustResize보다 더 세밀한 제어가 가능합니다.
  4. 화면의 목적(일반 화면, 입력 폼, 풀스크린 배경 등)에 따라 systemBarsPadding, statusBarsPadding, navigationBarsPadding, imePadding을 적절히 조합하는 것이 중요합니다.