Android 15·16 Edge-to-edge 화면 겹침 해결 — XML, BottomNavigationView, WindowInsets 실전 적용

Android 앱의 targetSdk를 35 또는 36으로 올린 뒤 기존에는 정상적으로 표시되던 화면이 상태 표시줄이나 하단 내비게이션 바와 겹치는 문제가 발생할 수 있습니다.

대표적인 증상은 다음과 같습니다.

  • Toolbar의 제목이 상태 표시줄과 겹침

  • BottomNavigationView가 시스템 내비게이션 영역과 겹침

  • RecyclerView의 마지막 항목이 하단에 가려짐

  • Fragment 화면마다 상단 여백이 다르게 적용됨

  • 키보드가 나타났을 때 입력창이나 버튼이 가려짐

  • 패딩을 추가했더니 오히려 빈 공간이 두 배로 늘어남

이 문제는 Android 15부터 강화된 Edge-to-edge 화면 정책과 관련이 있습니다.

이번 글에서는 Kotlin과 XML 레이아웃을 사용하는 기존 Android 프로젝트를 기준으로 WindowInsetsCompat를 이용해 화면 겹침 문제를 해결하는 방법을 정리하겠습니다.

특히 BottomNavigationView + NavHostFragment + RecyclerView 구조의 앱에 적용할 수 있는 코드를 중심으로 설명합니다.

작성 기준: 2026년 10월


1. Android 15부터 무엇이 달라졌을까?

기존 Android 앱에서는 시스템이 상태 표시줄과 내비게이션 바 영역을 제외한 공간에 앱 콘텐츠를 배치하는 경우가 많았습니다.

그러나 Android 15(API 35) 기기에서 targetSdk 35 이상인 앱은 Edge-to-edge가 기본 적용됩니다.

이제 앱의 화면이 시스템 바 뒤까지 확장됩니다.

Android 16(API 36)을 타깃으로 하는 앱은 Android 16 기기에서 Edge-to-edge 적용을 해제할 수 없습니다.

따라서 기존처럼 시스템 바 영역이 자동으로 제외된다고 가정한 레이아웃은 수정해야 합니다.

Android 버전별 차이

환경Edge-to-edge 동작
Android 14 이하앱 설정과 구현 방식에 따라 다름
Android 15 + targetSdk 35 이상기본적으로 강제 적용
Android 16 + targetSdk 36 이상강제 적용, 기존 opt-out 사용 불가

Android 15에서는 windowOptOutEdgeToEdgeEnforcement라는 임시 회피 옵션을 사용할 수 있었습니다.

하지만 Android 16을 타깃으로 하고 Android 16에서 실행하는 앱에서는 이 옵션이 더 이상 동작하지 않습니다.

따라서 장기적으로는 Edge-to-edge를 회피하는 것이 아니라, Insets를 올바르게 처리하는 방향으로 수정해야 합니다.

2. WindowInsets란?

WindowInsets는 시스템 UI가 화면에서 차지하는 영역을 앱에 알려주는 정보입니다.

예를 들어 상태 표시줄 높이가 70px, 하단 내비게이션 영역이 60px이라면 해당 영역을 고려해 앱 콘텐츠를 배치해야 합니다.

실제 값은 기기, 화면 방향, 내비게이션 방식에 따라 달라집니다.

주요 Insets 유형은 다음과 같습니다.

Insets 유형설명
statusBars()상단 상태 표시줄
navigationBars()하단 또는 측면 내비게이션 바
systemBars()상태 표시줄, 내비게이션 바 등의 시스템 바
displayCutout()카메라 홀이나 노치 영역
ime()소프트 키보드
systemGestures()시스템 제스처 영역

앱에서는 WindowInsetsCompat를 통해 Android 버전에 따른 차이를 줄이면서 이 정보를 처리할 수 있습니다.

3. 먼저 프로젝트 설정 확인하기

app/build.gradle.kts에서 SDK 설정을 확인합니다.

android {
    namespace = "com.example.edgeapp"
    compileSdk = 36

    defaultConfig {
        applicationId = "com.example.edgeapp"
        minSdk = 24
        targetSdk = 36
    }
}

위 코드는 SDK 설정 부분만 발췌한 예시입니다.

Insets 처리를 위해서는 AndroidX Core 라이브러리가 필요합니다.

dependencies {
    implementation("androidx.core:core-ktx:<version>")
}

<version>은 프로젝트에서 사용하는 AndroidX Core의 안정 버전으로 변경합니다.

또한 다음 import를 사용합니다.

import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding

4. Activity에서 Edge-to-edge 활성화하기

기존 Android 버전에서도 일관된 동작을 테스트하려면 enableEdgeToEdge()를 사용하는 것이 좋습니다.

import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

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

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

enableEdgeToEdge()는 AndroidX Activity에서 제공하는 확장 함수입니다. 프로젝트의 AndroidX Activity 버전이 해당 API를 지원해야 합니다.

Android 15 이상에서는 Edge-to-edge가 강제 적용되는 환경이 있지만, 이 함수를 사용하면 이전 Android 버전에서도 일관된 화면 동작을 구성하는 데 도움이 됩니다.

중요한 점은 enableEdgeToEdge()만 호출한다고 겹침 문제가 자동으로 해결되는 것은 아니라는 것입니다.

시스템 바 뒤로 화면을 확장하는 것과, 중요한 콘텐츠가 시스템 바에 가려지지 않도록 처리하는 것은 별개의 작업입니다.

5. XML 기반 BottomNavigationView 레이아웃 구성

다음과 같은 구조를 가진 앱을 가정하겠습니다.

MainActivity
 └── ConstraintLayout
      ├── Toolbar
      ├── NavHostFragment
      └── BottomNavigationView

실제 XML 예시는 다음과 같습니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbar"
        android:layout_width="0dp"
        android:layout_height="?attr/actionBarSize"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"
        app:layout_constraintTop_toBottomOf="@id/toolbar"
        app:layout_constraintBottom_toTopOf="@id/bottomNavigation"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:menu="@menu/bottom_nav_menu"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

이 XML은 구조를 설명하기 위한 예시입니다. nav_graph와 bottom_nav_menu는 프로젝트에 실제로 존재해야 합니다.

Edge-to-edge 환경에서 Toolbar는 상단 상태 표시줄과 겹칠 수 있고, BottomNavigationView는 하단 시스템 내비게이션 영역과 겹칠 수 있습니다.

이 문제를 해결하기 위해 Insets를 적용합니다.

6. 가장 간단한 해결 방법: 루트 레이아웃에 Insets 적용

먼저 기존 XML 레이아웃을 최대한 유지하면서 화면 겹침을 해결하는 방법입니다.

val root = findViewById<View>(R.id.main)

val initialLeft = root.paddingLeft
val initialTop = root.paddingTop
val initialRight = root.paddingRight
val initialBottom = root.paddingBottom

ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->

    val bars = insets.getInsets(
        WindowInsetsCompat.Type.systemBars() or
            WindowInsetsCompat.Type.displayCutout()
    )

    view.updatePadding(
        left = initialLeft + bars.left,
        top = initialTop + bars.top,
        right = initialRight + bars.right,
        bottom = initialBottom + bars.bottom
    )

    insets
}

이 방법은 루트 레이아웃 안의 모든 콘텐츠를 시스템 바 안쪽으로 이동시킵니다.

기존 레이아웃을 빠르게 복구할 때 유용합니다.

하지만 한계도 있습니다.

첫째, 앱 배경이 시스템 바 뒤까지 자연스럽게 이어지지 않을 수 있습니다.

둘째, BottomNavigationView처럼 Insets를 자동으로 처리하는 컴포넌트와 중복될 수 있습니다.

셋째, Fragment 내부에서 Insets를 추가로 처리하면 화면에 불필요한 여백이 생길 수 있습니다.

따라서 이 방법은 기존 화면을 빠르게 보정하기 위한 기본 방식으로 이해하고, 실제 앱 구조에 따라 적용 범위를 조정하는 것이 좋습니다.

7. BottomNavigationView의 Insets 중복 적용 문제

BottomNavigationView는 Material Components 라이브러리에서 제공하는 UI 컴포넌트입니다.

공식 문서에 따르면 BottomNavigationView를 포함한 여러 Material 컴포넌트는 시스템 Insets를 자동으로 처리합니다.

따라서 루트 레이아웃에 하단 패딩을 적용한 뒤 BottomNavigationView에도 같은 하단 Insets를 추가하면 여백이 중복될 수 있습니다.

예를 들어 다음과 같은 코드는 주의해야 합니다.

ViewCompat.setOnApplyWindowInsetsListener(
    bottomNavigation
) { view, insets ->

    val bars = insets.getInsets(
        WindowInsetsCompat.Type.navigationBars()
    )

    view.updatePadding(
        bottom = view.paddingBottom + bars.bottom
    )

    insets
}

이 코드에는 두 가지 문제가 있습니다.

첫째, 이미 자동으로 적용된 Insets가 있을 수 있습니다.

둘째, view.paddingBottom + bars.bottom을 반복 실행하면 패딩이 누적될 수 있습니다.

Insets 리스너는 한 번만 호출된다고 보장되지 않습니다.

권장 방법

Material Components의 BottomNavigationView를 사용한다면 먼저 기본 동작을 확인합니다.

  • BottomNavigationView가 내비게이션 바 영역을 올바르게 처리하는가?

  • 실제 콘텐츠가 시스템 바에 가려지는가?

  • 루트 레이아웃과 BottomNavigationView에 같은 Insets가 중복 적용되어 있는가?

정상적으로 처리된다면 별도의 하단 패딩 코드를 추가하지 않는 편이 좋습니다.

반대로 커스텀 하단 메뉴나 직접 만든 버튼 컨테이너를 사용한다면 navigationBars() Insets를 직접 적용해야 할 수 있습니다.

8. Toolbar가 상태 표시줄과 겹칠 때

상단 Toolbar가 상태 표시줄과 겹친다면 상단 Insets를 처리해야 합니다.

다음은 Toolbar의 상단 마진을 조정하는 예시입니다.

val toolbar = findViewById<View>(R.id.toolbar)

val params = toolbar.layoutParams as
    ViewGroup.MarginLayoutParams

val initialTopMargin = params.topMargin

ViewCompat.setOnApplyWindowInsetsListener(
    toolbar
) { view, insets ->

    val statusBars = insets.getInsets(
        WindowInsetsCompat.Type.statusBars()
    )

    val layoutParams = view.layoutParams as
        ViewGroup.MarginLayoutParams

    layoutParams.topMargin =
        initialTopMargin + statusBars.top

    view.layoutParams = layoutParams

    insets
}

이 방법은 Toolbar 자체를 상태 표시줄 아래로 이동시킵니다.

하지만 디자인상 Toolbar의 배경을 상태 표시줄 뒤까지 확장하고 싶다면 Toolbar에 패딩을 적용하거나 상위 컨테이너를 별도로 구성하는 방식이 더 적절할 수 있습니다.

또한 AppBarLayout을 사용하는 경우에는 다음과 같이 설정할 수 있습니다.

<com.google.android.material.appbar.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fitsSystemWindows="true">

    <!-- Toolbar -->

</com.google.android.material.appbar.AppBarLayout>

AppBarLayout은 BottomNavigationView와 달리 Insets를 자동으로 처리하지 않으므로 공식 문서에서도 fitsSystemWindows 적용을 안내합니다.

다만 동일한 상단 Insets를 별도 리스너에서도 처리하지 않도록 주의해야 합니다.

9. RecyclerView 마지막 항목이 가려지는 문제

Edge-to-edge 환경에서는 RecyclerView의 마지막 항목이 하단 내비게이션 영역 뒤로 들어갈 수 있습니다.

특히 BottomNavigationView가 없는 전체 화면 목록에서 자주 발생합니다.

이 경우 RecyclerView에 하단 패딩을 적용합니다.

XML 설정

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false" />

clipToPadding="false"를 사용하면 콘텐츠가 패딩 영역까지 자연스럽게 스크롤될 수 있습니다.

Kotlin 코드

val recyclerView =
    findViewById<RecyclerView>(R.id.recyclerView)

val initialBottom = recyclerView.paddingBottom

ViewCompat.setOnApplyWindowInsetsListener(
    recyclerView
) { view, insets ->

    val bars = insets.getInsets(
        WindowInsetsCompat.Type.navigationBars()
    )

    view.updatePadding(
        bottom = initialBottom + bars.bottom
    )

    insets
}

이 코드는 RecyclerView가 화면 하단까지 확장되는 구조에서 사용할 수 있습니다.

만약 RecyclerView가 이미 BottomNavigationView 위까지만 배치된다면 하단 내비게이션 Insets를 추가로 적용할 필요가 없을 수 있습니다.

RecyclerView가 실제로 어떤 영역까지 배치되는지 확인한 뒤 Insets를 적용해야 합니다.

10. Fragment마다 Insets를 적용해야 할까?

BottomNavigationView와 NavHostFragment를 사용하는 앱에서는 여러 Fragment가 하나의 Activity 안에서 전환됩니다.

이 경우 모든 Fragment에 동일한 Insets 코드를 작성하는 것은 비효율적일 수 있습니다.

권장하는 방식은 화면 구조에 따라 역할을 나누는 것입니다.

Activity에서 처리할 항목

  • 공통 Toolbar

  • 공통 BottomNavigationView

  • 공통 레이아웃 컨테이너

Fragment에서 처리할 항목

  • Fragment 전용 RecyclerView

  • 전체 화면 이미지

  • 입력창과 키보드

  • Fragment 내부의 고정 버튼

  • 화면별 커스텀 레이아웃

예를 들어 공통 Toolbar와 BottomNavigationView를 Activity에서 처리했다면 Fragment에서는 해당 영역의 Insets를 다시 적용하지 않는 것이 좋습니다.

Fragment의 루트가 이미 시스템 바를 제외한 안전한 영역에 배치되어 있다면 추가 처리가 필요하지 않을 수 있습니다.

11. Fragment에서 RecyclerView Insets 적용하기

Fragment 전용 RecyclerView가 시스템 바 뒤까지 확장되는 화면이라면 다음과 같이 처리할 수 있습니다.

class ListFragment : Fragment(R.layout.fragment_list) {

    override fun onViewCreated(
        view: View,
        savedInstanceState: Bundle?
    ) {
        super.onViewCreated(view, savedInstanceState)

        val recyclerView =
            view.findViewById<RecyclerView>(
                R.id.recyclerView
            )

        val initialBottom =
            recyclerView.paddingBottom

        ViewCompat.setOnApplyWindowInsetsListener(
            recyclerView
        ) { target, insets ->

            val navigationBars = insets.getInsets(
                WindowInsetsCompat.Type.navigationBars()
            )

            target.updatePadding(
                bottom = initialBottom +
                    navigationBars.bottom
            )

            insets
        }
    }
}

이 코드는 Fragment의 RecyclerView가 화면 하단 시스템 바까지 확장되는 경우를 가정합니다.

BottomNavigationView가 RecyclerView 아래에 이미 배치되어 있다면 해당 Insets가 중복될 수 있으므로 화면 구조에 맞게 조정해야 합니다.

12. ViewPager2를 사용하는 경우

ViewPager2로 여러 Fragment를 전환하는 앱에서는 Insets 처리 위치가 더욱 중요합니다.

예를 들어 다음 구조를 생각해 보겠습니다.

MainActivity
 └── ViewPager2
      ├── HomeFragment
      ├── SearchFragment
      └── SettingsFragment

Activity의 루트 레이아웃에 전체 Insets를 적용했다면 ViewPager2 내부 Fragment는 이미 시스템 바를 제외한 영역에 배치될 수 있습니다.

이 상태에서 각 Fragment에 같은 Insets를 추가하면 여백이 중복됩니다.

반면 ViewPager2가 전체 화면으로 확장되어 있다면 각 Fragment에서 필요한 영역을 처리할 수 있습니다.

중요한 것은 Insets를 무조건 Activity 또는 Fragment 중 한 곳에서 처리하는 것이 아니라, 실제로 시스템 바 뒤에 배치되는 View가 어디인지 확인하는 것입니다.

13. 키보드가 나타날 때 입력창이 가려지는 문제

검색 화면이나 메모 입력 화면에서는 키보드가 표시될 때 하단 입력창이 가려질 수 있습니다.

이때는 WindowInsetsCompat.Type.ime()을 사용합니다.

다음은 화면 하단에 고정된 입력 컨테이너를 조정하는 예시입니다.

val inputContainer =
    findViewById<View>(R.id.inputContainer)

val initialBottom =
    inputContainer.paddingBottom

ViewCompat.setOnApplyWindowInsetsListener(
    inputContainer
) { view, insets ->

    val imeVisible = insets.isVisible(
        WindowInsetsCompat.Type.ime()
    )

    val bottomInset = if (imeVisible) {
        insets.getInsets(
            WindowInsetsCompat.Type.ime()
        ).bottom
    } else {
        insets.getInsets(
            WindowInsetsCompat.Type.navigationBars()
        ).bottom
    }

    view.updatePadding(
        bottom = initialBottom + bottomInset
    )

    insets
}

이 코드는 하단 입력 컨테이너가 화면 하단까지 배치되는 구조를 가정합니다.

키보드가 표시되면 IME Insets를 적용하고, 키보드가 사라지면 내비게이션 바 Insets를 적용합니다.

단, 기존 adjustResize 동작이나 상위 레이아웃의 Insets 처리와 중복되면 입력창이 과도하게 이동할 수 있습니다.

따라서 실제 화면 구조에 맞춰 한 곳에서만 하단 여백을 관리하는 것이 좋습니다.

14. Insets 패딩이 계속 증가하는 문제

WindowInsets를 적용할 때 가장 흔한 실수 중 하나가 기존 패딩에 Insets 값을 반복해서 더하는 것입니다.

다음 코드는 문제가 발생할 수 있습니다.

view.updatePadding(
    bottom = view.paddingBottom + bars.bottom
)

Insets 리스너가 다시 호출되면 이미 추가한 패딩에 Insets가 또 더해집니다.

이로 인해 화면을 회전하거나 키보드를 열고 닫을 때 여백이 점점 커질 수 있습니다.

올바른 방법

리스너를 등록하기 전에 원래 패딩을 저장합니다.

val initialBottom = view.paddingBottom

ViewCompat.setOnApplyWindowInsetsListener(
    view
) { target, insets ->

    val bars = insets.getInsets(
        WindowInsetsCompat.Type.navigationBars()
    )

    target.updatePadding(
        bottom = initialBottom + bars.bottom
    )

    insets
}

이렇게 하면 Insets가 여러 번 전달되어도 원래 패딩을 기준으로 계산하므로 값이 누적되지 않습니다.

15. WindowInsetsCompat.CONSUMED 사용 시 주의사항

Insets 리스너에서는 일반적으로 전달받은 insets를 반환합니다.

insets

이렇게 하면 하위 View도 필요한 Insets를 전달받을 수 있습니다.

반면 다음과 같이 반환할 수도 있습니다.

WindowInsetsCompat.CONSUMED

이 경우 해당 Insets를 소비한 것으로 처리합니다.

문제는 Android 10(API 29) 이하에서 부모 ViewGroup이 Insets를 소비하면 형제 View에 Insets가 정상적으로 전달되지 않을 수 있다는 점입니다.

따라서 특별한 이유가 없다면 무조건 CONSUMED를 반환하는 방식은 피하는 것이 좋습니다.

Insets 소비가 필요한 구조라면 AndroidX Core의 ViewGroupCompat.installCompatInsetsDispatch()를 활용해 하위 호환성을 고려할 수 있습니다.

16. 기존 fitsSystemWindows를 모두 제거해야 할까?

기존 XML 프로젝트에서는 다음 속성을 자주 사용합니다.

android:fitsSystemWindows="true"

이 속성은 일부 레이아웃에서 시스템 창 영역을 고려하도록 도와줍니다.

하지만 모든 View에 무조건 추가하는 방식은 권장하지 않습니다.

특히 다음과 같은 문제가 발생할 수 있습니다.

  • 부모와 자식에서 Insets 중복 적용

  • Toolbar 상단 여백 중복

  • RecyclerView 스크롤 영역 축소

  • BottomNavigationView 아래 빈 공간 발생

반대로 AppBarLayout처럼 공식적으로 fitsSystemWindows 사용을 안내하는 컴포넌트도 있습니다.

따라서 기존 속성을 모두 삭제하거나 모두 추가하는 방식보다는 실제 View 계층과 Insets 처리 방식을 확인해야 합니다.

17. 실제 기기에서 테스트하는 방법

Edge-to-edge는 에뮬레이터에서 정상적으로 보이더라도 실제 기기에서 차이가 발생할 수 있습니다.

특히 내비게이션 방식에 따라 화면 하단 영역이 달라집니다.

테스트 환경

테스트 환경확인 항목
Android 14이전 버전 호환성
Android 15Edge-to-edge 강제 적용
Android 16opt-out 없이 정상 표시
제스처 내비게이션하단 제스처 영역
3버튼 내비게이션내비게이션 바 배경 및 겹침
가로 화면좌우 Insets와 화면 잘림
키보드 표시입력창과 버튼 가림

Insets 값 로그 출력

문제를 분석할 때는 실제 Insets 값을 로그로 출력하면 도움이 됩니다.

ViewCompat.setOnApplyWindowInsetsListener(
    findViewById(R.id.main)
) { view, insets ->

    val systemBars = insets.getInsets(
        WindowInsetsCompat.Type.systemBars()
    )

    Log.d(
        "EdgeToEdge",
        "top=${systemBars.top}, " +
            "bottom=${systemBars.bottom}, " +
            "left=${systemBars.left}, " +
            "right=${systemBars.right}"
    )

    insets
}

이 코드는 Insets 값을 확인하기 위한 디버깅 예시입니다.

로그에 출력된 값은 픽셀 단위이며 기기별로 달라질 수 있습니다.

18. 출시 전 점검 체크리스트

  • targetSdk 35 또는 36에서 화면 확인

  • Toolbar가 상태 표시줄과 겹치지 않는지 확인

  • BottomNavigationView가 정상적으로 표시되는지 확인

  • RecyclerView 마지막 항목이 가려지지 않는지 확인

  • Fragment 전환 시 여백이 달라지지 않는지 확인

  • ViewPager2 내부 화면의 Insets 중복 여부 확인

  • 키보드 표시 시 입력창이 가려지지 않는지 확인

  • 제스처 내비게이션과 3버튼 내비게이션 테스트

  • 화면 회전 시 패딩이 누적되지 않는지 확인

  • Android 14 이하에서도 레이아웃이 정상적인지 확인

  • Android 16에서 Edge-to-edge opt-out 없이 동작하는지 확인

19. 마무리

Android 15와 Android 16의 Edge-to-edge 대응은 단순히 화면 상단과 하단에 패딩을 추가하는 작업이 아닙니다.

기존 XML 레이아웃의 구조를 이해하고, 시스템 바 뒤까지 확장되는 영역과 실제로 보호해야 할 콘텐츠를 구분해야 합니다.

특히 BottomNavigationView, Toolbar, RecyclerView, Fragment를 함께 사용하는 앱에서는 Insets를 중복 적용하지 않는 것이 중요합니다.

실무에서는 다음 순서로 접근하는 것이 좋습니다.

  1. 현재 화면에서 어떤 View가 시스템 바와 겹치는지 확인합니다.

  2. Material Components가 Insets를 자동 처리하는지 확인합니다.

  3. 필요한 View에만 WindowInsetsCompat를 적용합니다.

  4. 기존 패딩을 기준으로 Insets를 계산해 누적을 방지합니다.

  5. Android 14·15·16과 여러 내비게이션 방식에서 검증합니다.

기존 앱의 targetSdk를 올릴 때 화면 겹침 문제가 발생한다면, 레이아웃 전체를 수정하기 전에 Insets가 적용되는 위치부터 확인하는 것이 문제 해결에 도움이 됩니다.


참고 자료

태그: Android, Kotlin, Android 15, Android 16, Edge-to-edge, WindowInsets, WindowInsetsCompat, XML Layout, BottomNavigationView, RecyclerView, Fragment, ViewPager2