edge-to-edge
edge-to-edge 란
① edge-to-edge 란 앱 컨텐츠를 system bar(status bar + navigation bar) 영역까지 확장하여 표시하게 만드는 것을 말합니다. 쉽게 말해 status bar 와 navigation bar 를 투명하게 만들어서 앱 컨텐츠가 보이게 만드는 것을 말합니다.
edge-to-edge 구현
① system bar 의 색상 변경
앱이 edge-to-edge 로 동작할 때 앱 컨텐츠를 볼 수 있도록 status bar 와 navigation bar 의 색상을 투명하게 만들어야 합니다. 앱이 이 단계를 수행한 후 시스템은 Gestrue Navigation 모드에서는 navigation bar 뒤에 있는 컨텐츠에 따라 색상을 변경하는 dynamic color adaptation 을 적용하여 컨텐츠를 보호합니다. 예를들어 navigation bar 가 밝은 색상의 컨텐츠 위에 있으면 어두운 색상으로 변경됩니다. 그리고 Three Button 모드에서는 translucent scrim 을 적용하여 컨텐츠를 보호합니다. translucent scrim 은 navigation bar 를 반투명한 색상으로 만들어서 컨텐츠를 보호합니다. 하지만 translucent scrim 은 SDK 버전 29 이상에서만 보인다는 점을 명심하셔야 합니다.
//API Level 29 이상 → values-v29/themes.xml
<style name="Theme.MyApp" parent="Theme.EdgeToEdge">
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
</style>
//API Level 29 미만 → values/themes.xml
<style name="Theme.MyApp" parent="Theme.EdgeToEdge">
<item name="android:navigationBarColor">#B3FFFFFF</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
</style>
② 전체 화면으로 표시하도록 요청
컨텐츠는 status bar 아랫쪽에서 navigation bar 윗쪽 사이에 표시되므로 앱 컨텐츠가 기기 화면의 전체 높이로 표시되도록 요청해야 합니다.
fun makeFullscreen() {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowCompat.setDecorFitsSystemWindows(window, false)
} else {
binding.root.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}
}
③ system UI 와 겹치는 것을 피하도록 Insets 값 적용
2번까지 진행하면 앱 컨텐츠가 전체 화면으로 표시되기 때문에 앱 최상단 또는 최하단에 있는 View 가 system UI 와 겹쳐서 보이게 됩니다. 대표적으로 보통 우측 하단에 위치한 Floating Action Button 같은 경우에는 아래의 스크린샷과 같이 system UI 와 겹치게 됩니다. edge-to-edge 를 구현하기 위한 마지막 단계에서 system UI 와 겹치는 부분을 해결합니다. 이때 등장하는 개념이 Insets 입니다. Insets 는 간단히 말하면 컨텐츠를 이동할 정도를 알려주는 값의 모음입니다. 문제를 해결하기 위해 사용할 Insets 은 System Window Insets 입니다. System Window Insets 는 앱에서 system UI 가 표시되는 위치를 알려줍니다. 이 Insets 은 클릭 가능한 View 를 가장자리에서 이동시키는 것에 사용됩니다.
System Window Insets 을 이용하여 View 를 가장자리에서 이동시키기 위해서는 ViewCompat.setOnApplyWindowInsetsListener(View, OnApplyWindowInsetsListener) 메서드를 사용하여 얻은 Insets 값을 Root View(예: ConstraintLayout) 의 updatePadding 메서드를 사용하여 Layout 자체에 패딩값을 통해 View 를 가장자리에서 이동시키는 방법과 가장자리에서 이동시키고 싶은 View 자체에 margin 값을 설정하여 가장자리에서 이동시키는 방법 2가지를 통해 system UI 와 겹치는 문제를 해결할 수 있습니다. 두번째 방법을 사용하게 된다면 View 자체에 margin 값을 다시 적용하는 것이므로 기존에 적용되어 있던 margin 값은 덮어씌어지기 때문에 기존에 적용되어있던 margin 값을 더하여 새로운 margin 을 설정해야 합니다. 첫번째 방법 역시 기존에 padding 을 유지한 채로 얻어온 Insets 값 만큼의 패딩을 추가적으로 적용시켜줘야 기존에 설정된 패딩값이 정상적으로 적용됩니다.
fun moveToEdgeByPadding() {
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets ->
view.updatePadding(
bottom = insets.systemWindowInsets.bottom + view.padding
)
insets
}
}
fun moveToEdgeByMargin() {
ViewCompat.setOnApplyWindowInsetsListener(binding.fab) { view, insets ->
val lp = view.layoutParams as ConstraintLayout.LayoutParams
lp.bottomMargin = insets.systemWindowInsets.bottom
view.layoutParams = lp
insets
}
}