🤖안드로이드🤖

[안드로이드] WindowInsets 조정하기

bbooyaaa 2025. 8. 10. 23:02

왜 Inset 처리가 필요한가?

스마트폰 화면 상단과 하단에는 시스템 UI(상태바·네비게이션바)가 있습니다.

앱 화면이 이 영역까지 확장되면(Edge-to-Edge) 콘텐츠가 시스템 UI와 겹칠 수 있어, 영역을 고려한 레이아웃 조정이 필요합니다.

영역을 고려하지 않고 코드를 작성하게 되면

상단 Toolbar가 상태바 아래로 파묻히거나 하단 버튼이 네비게이션바와 겹쳐서 클릭하기 어려운 경우가 생길 수 있겠죠.

따라서 Inset 처리가 필요합니다!

왼쪽은 Inset을 고려하지 않았을 때, 오른쪽은 고려했을 때입니다. 확실히 차이가 나죠?

기존에는 어떻게 했을까요?(Accompanist Insets)

예전엔 Accompanist Insets라는 라이브러리를 써서

  • 상태바(Status Bar) 높이만큼
  • 네비게이션 바(Navigation Bar) 높이만큼 패딩 주기

를 쉽게 했습니다.

Modifier.statusBarsPadding()
Modifier.navigationBarsPadding()

그런데 Accompanist Insets는 deprecated 됐고, Jetpack Compose와 AndroidX가 WindowInsets API를 직접 지원하게 됐어요.

공식 API로 처리하는 방법

Compose 1.2+부터는 WindowInsets 처리를 위한 표준 Modifier들이 있습니다.

enableEdgeToEdge()
Scaffold(
    modifier = modifier
        .fillMaxSize()
        .windowInsetsPadding(WindowInsets.statusBars),
    ...
    bottomBar = {
        Modifier.navigationBarsPadding()
    }
)
  • enableEdgeToEdge() -> 화면을 시스템 바(상태바·네비게이션바)까지 확장해서 그리게 함
  • windowInsetsPadding(WindowInsets.statusBars) -> 상태바 높이만큼 패딩
  • navigationBarsPadding() -> 네비게이션바 높이만큼 패딩

이건 Compose API이고, Accompanist가 아니라 이미 공식 API이긴합니다.

이 방식처럼 Accompanist 없이도 Compose 내부에서 안전하게 Insets를 처리할 수 있죠.

하지만 Compose 내부에서 뿐만 아니라 바깥에서도 제어가 필요하다면 어떨까요?

대부분의 경우 Compose Modifier로 충분하지만,

  • 스플래시 화면: 로고를 화면 정중앙에 배치하고 싶은데 상태바/네비게이션바 때문에 위치가 어긋남
  • Compose + XML 혼합 화면: XML 뷰에도 Insets 처리가 필요
  • 전체 화면 동영상: 하단 컨트롤 버튼이 네비게이션바와 겹치는 문제 해결

와 같은 경우에는 바깥에서 제어를 해줘야겠죠?

따라서 여기서 한 단계 더 나아가, Compose 바깥에서도 inset 조정하는 법에 대해 알아볼게요.

setOnApplyWindowInsetsListener 활용하기

ViewCompat.setOnApplyWindowInsetsListener는 시스템 UI 영역 변화를 감지하고, 그 값에 맞춰 레이아웃을 동적으로 조정할 수 있습니다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()

    ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, insets ->
        val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
        // systemBars.top  → 상태바 높이
        // systemBars.bottom → 내비게이션바 높이

        // 예: 이 값을 Compose ViewModel에 전달해 UI 상태로 관리 가능
        insets
    }

    setContent {
        MyApp()
    }
}

이렇게 하면 상태바/내비게이션바 높이 정보를 Compose 외부에서 확보하고,

Compose UI나 다른 View 레이아웃에 반영할 수 있습니다.

Compose와 Acticity Insets 연동하기

Activity에서 측정한 Insets 값을 Compose로 넘기려면 ViewModel이나 CompositionLocal을 사용할 수 있습니다.

class InsetsViewModel : ViewModel() {
    private val _insets = MutableStateFlow(Insets())
    val insets: StateFlow<Insets> = _insets

    fun update(insets: Insets) {
        _insets.value = insets
    }
}

data class Insets(val top: Int = 0, val bottom: Int = 0)
val vm: InsetsViewModel by viewModels()
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, insets ->
    val sb = insets.getInsets(WindowInsetsCompat.Type.systemBars())
    vm.update(Insets(sb.top, sb.bottom))
    insets
}
val insets by vm.insets.collectAsState()
Spacer(modifier = Modifier.height(insets.top.dp)) // 상태바 높이만큼 Spacer

이런 식으로요.

 

결론적으로,

setOnApplyWindowInsetsListener를 쓰면 Activity나 View 레벨에서 시스템 UI 영역 정보(상태바·네비게이션바 크기)를 받아서, 그걸 원하는 방식으로 가공해서 적용할 수 있습니다.

즉, 각 화면·각 뷰마다:

  • 둘 다 적용 → 상태바 + 네비게이션바 높이만큼 패딩
  • 하나만 적용 → 예: 상단만 패딩, 하단은 그대로
  • 둘 다 빼기 → Edge-to-Edge로 꽉 채움
  • 값 변형 → 패딩 대신 margin이나 offset에 적용, 혹은 절반만 적용

다 가능합니다.

이제 Accompanist 없이도 공식 API + Acticity 제어만으로 edge-to-edge UI를 안전하고 유연하게 구성할 수 있겠죠? 모두 한번 활용해보시기를 추천드립니다.