
안드로이드 15(API 35) 타겟 부터는 엣지 투 엣지(edge-to-edge) 디자인이 기본값으로 적용되었습니다. 추후 스토어에 등록된 앱들이 API 35 버전으로 강제 될 예정이기 때문에 대응하는 과정을 작성해보겠습니다.
참고!
이 글은 build.gradle (:app)상에서 targetSdk = 35 환경에서 대응 작업을 했습니다.
이전 버전까지는 Status Bar, 하단의 Navigation Bar 영역만큼 자동으로 패딩이 적용되어 UI가 가려지지 않았지만, 안드로이드 15부터는 별도의 처리를 하지 않으면 시스템 바 아래에 UI가 위치해 가려질 수 있습니다.
Status Bar 예시
targetSdk 34이하 미대응 정상 상단바와 겹치는 문제 발생
Navigation Bar 예시
targetSdk 34이하 미대응 정상 하단 네비게이션과 겹치는 문제 발생
- fragment 없이 복수의 activity 로 구성된 프로젝트
- activity를 두고 복수의 fragment간 이동하는 Single Activity Architecture 프로젝트
- WebView를 사용하는 Hybrid 프로젝트
- xml + Compose 혼합된 고도화 중인 프로젝트
등의 구조에서 사용할 수 있습니다.
대응이 필요한 UI에 해당하는 xml 의 루트에 해당하는 레이아웃의 속성으로
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
추가 ⬇️
android:fitsSystemWindows="true"
... 생략
>
fitsSystemWindows="true" 지정하면 시스템 영역을 피해서 UI요소들이 배치됩니다.
다만 Android 15 환경에서는, fitsSystemWindows 사용은 더 이상 권장되지 않으며, WindowInsets 기반의 수동 처리가 공식적으로 안내되고 있습니다.
BaseActivity(Fragment) 같은 공통 코드의 onCreate()에 ViewCompat를 통해 처리합니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivitySplashBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets ->
val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.setPadding(
systemInsets.left,
systemInsets.top,
systemInsets.right,
systemInsets.bottom
)
insets
}
}
단 유의할 점은 신규 개발이 아닌 대응을 하는 시점에서 기존에 지정한 xml 상의 android:paddingHorizontal 같은 좌우 여백 값들이 무시됩니다.
그렇기 때문에 기존 view의 설정된 여백 값을 가져와 적용해주거나 여백 관리 방식을 바꿔야 합니다.
val initialPaddingLeft = view.paddingLeft
val initialPaddingRight = view.paddingRight
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(
initialPaddingLeft,
systemInsets.top,
initialPaddingRight,
systemInsets.bottom
)
insets
}
위처럼 정의된 값을 가져와 그대로 적용하거나 initialPaddingLeft + systemInsets.left 이런식으로 기존 값과 더하는 방식으로 처리하면 기존 값을 살리면서 의도에 맞게 대응하게 됩니다.
컴포저블의 Modifier에 붙여 처리하는 방식이며 두가지 처리가 있습니다
Column(
modifier = Modifier
// 1번 개별 처리
.statusBarsPadding()
.navigationBarsPadding()
// 2번 일괄 처리
.windowInsetsPadding(WindowInsets.safeDrawing
//.verticalScroll() 등등..
)
1번의 경우 상/하단 Inset 처리를 개별적으로 해주는 방식입니다.
하단 네비게이션을 사용하는 프로젝트에서 material3.NavigationBar 같은 컴포넌트를 사용하는 경우 컴포넌트가 자체적으로 하단 Inset을 고려해서 그려지기 때문에 별도의 하단 처리가 필요 없어질 수도 있습니다. 그럴 때 statusBarsPadding() 만 사용하여 개별 처리 할 수 있겠습니다.
2번의 경우 일괄적으로 처리하나 파라미터 값에 따라 처리되는 범위가 달라지며 대표적으로는 가장 넓은 범위의 안전영역을 대응할때 쓰는 safeDrawing가 있습니다.
그외:
Scaffold는 snackbarHost, topBar, bottomBar 등 기본 UI를 그리기에 유리하다는 장점을 가지고 있으며, 시스템 인셋에 대한 처리를 지원합니다.
Scaffold(
content = { innerPadding ->
Box(
modifier = Modifier
.fillMaxSize()
// 추가 ⬇️
.padding(innerPadding)
)
...
}
)
Scaffold 필수 슬롯인 content는 메인 콘텐츠를 할당하는 영역이며, 람다 파라미터로 제공하는 PaddingValues타입의 innerPadding를 통해서 위 처럼 시스템 인셋 처리를 할 수 있겠습니다.
동작 변경사항: Android 15
뷰에서 더 넓은 화면에 콘텐츠 표시
Compose의 더 넓은 화면
지원 중단된 API