Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DroidKnights 2025 - 나도 edgeToEdge 적용하기 ...

Avatar for ezhoon ezhoon
June 17, 2025
220

DroidKnights 2025 - 나도 edgeToEdge 적용하기 싫어. 근데 누군가는 해야 하잖아?

targetSdk35의 가장 큰 변경사항인 EdgeToEdge에 대해서 알아보았습니다.

Avatar for ezhoon

ezhoon

June 17, 2025
Tweet

Transcript

  1. 나 도 edgeToEdge 저 ᆨ 요 ᆼ 하 기 시

    ᆶ 어 . 근 데 누 구 ᆫ 가 는 해 야 하 자 ᆭ 아 ? 이지훈 SOOP
  2. 목차 1 EdgeToEdge 톺아보자 2 EdgeToEdge 적용기 3 Inset은 어떻게

    다뤄야 하는가? 4 적용하면서 생겼던 이슈들
  3. fun ComponentActivity.enableEdgeToEdge( statusBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = Color.TRANSPARENT, darkScrim

    = Color.TRANSPARENT ), navigationBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = DefaultLightScrim, darkScrim = DefaultDarkScrim ) ) { // EdgeToEdge 적용 부분 }
  4. fun ComponentActivity.enableEdgeToEdge( statusBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = Color.TRANSPARENT, darkScrim

    = Color.TRANSPARENT ), navigationBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = DefaultLightScrim, darkScrim = DefaultDarkScrim ) ) { // EdgeToEdge 적용 부분 }
  5. fun ComponentActivity.enableEdgeToEdge( statusBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = Color.TRANSPARENT, darkScrim

    = Color.TRANSPARENT ), navigationBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = DefaultLightScrim, darkScrim = DefaultDarkScrim ) ) { // EdgeToEdge 적용 부분 }
  6. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto(

    lightScrim = Color.TRANSPARENT, darkScrim = Color.TRANSPARENT, ), ) setContentView(binding.root) }
  7. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto(

    lightScrim = Color.TRANSPARENT, darkScrim = Color.TRANSPARENT, ), ) setContentView(binding.root) }
  8. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto(

    lightScrim = Color.TRANSPARENT, darkScrim = Color.TRANSPARENT, ), ) setContentView(binding.root) }
  9. @DoNotInline override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window,

    view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) window.statusBarColor = statusBarStyle.getScrimWithEnforcedContrast(statusBarIsDark) window.navigationBarColor = navigationBarStyle.getScrimWithEnforcedContrast(navigationBarIsDark) window.isStatusBarContrastEnforced = false window.isNavigationBarContrastEnforced = navigationBarStyle.nightMode == UiModeManager.MODE_NIGHT_AUTO WindowInsetsControllerCompat(window, view).run { isAppearanceLightStatusBars = !statusBarIsDark isAppearanceLightNavigationBars = !navigationBarIsDark }
  10. fun auto( @ColorInt lightScrim: Int, @ColorInt darkScrim: Int, detectDarkMode: (Resources)

    -> Boolean = { resources -> (resources.con fi guration.uiMode and Con fi guration.UI_MODE_NIGHT_MASK) == Con fi guration.UI_MODE_NIGHT_YES } ): SystemBarStyle { return SystemBarStyle( lightScrim = lightScrim, darkScrim = darkScrim, nightMode = UiModeManager.MODE_NIGHT_AUTO, detectDarkMode = detectDarkMode ) }
  11. fun dark(@ColorInt scrim: Int): SystemBarStyle { return SystemBarStyle( lightScrim =

    scrim, darkScrim = scrim, nightMode = UiModeManager.MODE_NIGHT_YES, detectDarkMode = { _ -> true } ) }
  12. fun light( @ColorInt scrim: Int, @ColorInt darkScrim: Int ): SystemBarStyle

    { return SystemBarStyle( lightScrim = scrim, darkScrim = darkScrim, nightMode = UiModeManager.MODE_NIGHT_NO, detectDarkMode = { _ -> false } ) }
  13. fun light( @ColorInt scrim: Int, @ColorInt darkScrim: Int ): SystemBarStyle

    { return SystemBarStyle( lightScrim = scrim, darkScrim = darkScrim, nightMode = UiModeManager.MODE_NIGHT_NO, detectDarkMode = { _ -> false } ) }
  14. /** * 내비게이션 바에 완전히 투명한 배경이 요청되었을 때, *

    시스템이 충분한 대비를 보장해야 하는지를 설정합니다. * 이 값으로 설정하면, 시스템은 이 앱의 콘텐츠와 내비게이션 바 간에 충분한 대비가 필요한지를 판단하여, * 대비를 확보하기 위해 스크림이 필요한지 여부를 결정하고, 적절한 내비게이션 바 배경색을 설정합니다. * 내비게이션 바 색상이 불투명하거나 반투명이면, 이 속성의 값은 아무런 효과를 미치지 않습니다. */ fun setNavigationBarContrastEnforced(enforceContrast: Boolean) { }
  15. /** * 내비게이션 바에 완전히 투명한 배경이 요청되었을 때, *

    시스템이 충분한 대비를 보장해야 하는지를 설정합니다. * 이 값으로 설정하면, 시스템은 이 앱의 콘텐츠와 내비게이션 바 간에 충분한 대비가 필요한지를 판단하여, * 대비를 확보하기 위해 스크림이 필요한지 여부를 결정하고, 적절한 내비게이션 바 배경색을 설정합니다. * 내비게이션 바 색상이 불투명하거나 반투명이면, 이 속성의 값은 아무런 효과를 미치지 않습니다. */ fun setNavigationBarContrastEnforced(enforceContrast: Boolean) { }
  16. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto(

    lightScrim = android.graphics.Color.TRANSPARENT, darkScrim = android.graphics.Color.TRANSPARENT, ), ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } setContentView(binding.root) }
  17. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto(

    lightScrim = android.graphics.Color.TRANSPARENT, darkScrim = android.graphics.Color.TRANSPARENT, ), ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } setContentView(binding.root) }
  18. fun ComponentActivity.enableEdgeToEdge( statusBarStyle: SystemBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT), navigationBarStyle: SystemBarStyle =

    SystemBarStyle.auto(DefaultLightScrim, DefaultDarkScrim) ) { val view = window.decorView val statusBarIsDark = statusBarStyle.detectDarkMode(view.resources) val navigationBarIsDark = navigationBarStyle.detectDarkMode(view.resources) val impl = Impl ?: if (Build.VERSION.SDK_INT >= 29) { EdgeToEdgeApi29() } else if (Build.VERSION.SDK_INT >= 26) { EdgeToEdgeApi26() } else if (Build.VERSION.SDK_INT >= 23) { EdgeToEdgeApi23() } else if (Build.VERSION.SDK_INT >= 21) { EdgeToEdgeApi21() } else { EdgeToEdgeBase() }.also { Impl = it } impl.setUp( statusBarStyle, navigationBarStyle, window, view, statusBarIsDark, navigationBarIsDark )
  19. private class EdgeToEdgeApi21 : EdgeToEdgeImpl { override fun setUp( statusBarStyle:

    SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } }
  20. private class EdgeToEdgeApi23 : EdgeToEdgeImpl { override fun setUp( statusBarStyle:

    SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } } private class EdgeToEdgeApi21 : EdgeToEdgeImpl { override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } }
  21. private class EdgeToEdgeApi26 : EdgeToEdgeImpl { override fun setUp( statusBarStyle:

    SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } } private class EdgeToEdgeApi23 : EdgeToEdgeImpl { override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } } private class EdgeToEdgeApi21 : EdgeToEdgeImpl { override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } }
  22. private class EdgeToEdgeApi29 : EdgeToEdgeImpl { override fun setUp( statusBarStyle:

    SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } } private class EdgeToEdgeApi26 : EdgeToEdgeImpl { override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } } private class EdgeToEdgeApi23 : EdgeToEdgeImpl { override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } } private class EdgeToEdgeApi21 : EdgeToEdgeImpl { override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } }
  23. private class EdgeToEdgeApi29 : EdgeToEdgeImpl { override fun setUp( statusBarStyle:

    SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } } private class EdgeToEdgeApi26 : EdgeToEdgeImpl { override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } } private class EdgeToEdgeApi23 : EdgeToEdgeImpl { override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } } private class EdgeToEdgeApi21 : EdgeToEdgeImpl { override fun setUp( statusBarStyle: SystemBarStyle, navigationBarStyle: SystemBarStyle, window: Window, view: View, statusBarIsDark: Boolean, navigationBarIsDark: Boolean ) { WindowCompat.setDecorFitsSystemWindows(window, false) // systembar 세팅 코드 } }
  24. EdgeToEdge 적용 전 1 . StatusBar 색상을 직접 제어하고 있다.

    2 . NavigationBar 색상의 이질감이 있다.
  25. EdgeToEdge 적용 전 1 . StatusBar 색상을 직접 제어하고 있다.

    2 . NavigationBar 색상의 이질감이 있다.
  26. fun updateStatusBarColorBasedOnFragment() { val currentFragment = supportFragmentManager .fragments .lastOrNull() val

    colorRes = when (currentFragment) { is WhiteStatusBarFragment -> R.color.white else -> R.color.default_status_bar } applyStatusBarColor(colorRes) }
  27. fun updateStatusBarColorBasedOnFragment() { val currentFragment = supportFragmentManager .fragments .lastOrNull() val

    colorRes = when (currentFragment) { is WhiteStatusBarFragment -> R.color.white else -> R.color.default_status_bar } applyStatusBarColor(colorRes) } fun updateStatusBarColorBasedOnFragment() { val currentFragment = supportFragmentManager .fragments .lastOrNull() val colorRes = when (currentFragment) { is WhiteStatusBarFragment -> R.color.white else -> R.color.default_status_bar } applyStatusBarColor(colorRes) }
  28. fun updateStatusBarColorBasedOnFragment() { val currentFragment = supportFragmentManager .fragments .lastOrNull() val

    colorRes = when (currentFragment) { is WhiteStatusBarFragment -> R.color.white else -> R.color.default_status_bar } applyStatusBarColor(colorRes) } fun updateStatusBarColorBasedOnFragment() { val currentFragment = supportFragmentManager .fragments .lastOrNull() val colorRes = when (currentFragment) { is WhiteStatusBarFragment -> R.color.white else -> R.color.default_status_bar } applyStatusBarColor(colorRes) } fun updateStatusBarColorBasedOnFragment() { val currentFragment = supportFragmentManager .fragments .lastOrNull() val colorRes = when (currentFragment) { is WhiteStatusBarFragment -> R.color.white else -> R.color.default_status_bar } applyStatusBarColor(colorRes) }
  29. EdgeToEdge 적용 후 1 . StatusBar 색상을 제어하고 있지 않다.

    2 . NavigationBar 색상을 맞닿은 View와 동일한 색상
  30. EdgeToEdge 적용 후 1 . StatusBar 색상을 제어하고 있지 않다.

    2 . NavigationBar 색상을 맞닿은 View와 동일한 색상
  31. StatusBar 아이콘 /** * If true, changes the foreground color

    of the status bars * to light so that the items on the bar can be read clearly. * If false, reverts to the default appearance. */ fun setAppearanceLightStatusBars(isLight: Boolean) { mImpl.setAppearanceLightStatusBars(isLight) } StatusBar 아이콘 혹은 텍스트의 색상을 변경 - true -> 어두운 색상 - false -> 밝은 색상
  32. fun getStatusBarHeight(window: Window): Int { val decorView = window.decorView return

    decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이
  33. fun getStatusBarHeight(window: Window): Int { val decorView = window.decorView return

    decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이
  34. fun getStatusBarHeight(window: Window): Int { val decorView = window.decorView return

    decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이
  35. fun getStatusBarHeight(window: Window): Int { val decorView = window.decorView return

    decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이 fun captureStatusBarBitmap( window: Window, height: Int, onResult: (Bitmap?) -> Unit ) { val rect = Rect(0, 0, window.decorView.width, height) val bmp = createBitmap( width = rect.width(), height = rect.height() ) PixelCopy.request(window, rect, bmp, { _ -> onResult(bmp) }, Handler(Looper.getMainLooper())) } 2 . StatusBar 높이 만큼 캡쳐
  36. 1 . StatusBar 영역 만큼 캡쳐 한다. fun getStatusBarHeight(window: Window):

    Int { val decorView = window.decorView return decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이 fun captureStatusBarBitmap( window: Window, height: Int, onResult: (Bitmap?) -> Unit ) { val rect = Rect(0, 0, window.decorView.width, height) val bmp = createBitmap( width = rect.width(), height = rect.height() ) PixelCopy.request(window, rect, bmp, { _ -> onResult(bmp) }, Handler(Looper.getMainLooper())) } 2 . StatusBar 높이 만큼 캡쳐
  37. 1 . StatusBar 영역 만큼 캡쳐 한다. fun getStatusBarHeight(window: Window):

    Int { val decorView = window.decorView return decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이 fun captureStatusBarBitmap( window: Window, height: Int, onResult: (Bitmap?) -> Unit ) { val rect = Rect(0, 0, window.decorView.width, height) val bmp = createBitmap( width = rect.width(), height = rect.height() ) PixelCopy.request(window, rect, bmp, { _ -> onResult(bmp) }, Handler(Looper.getMainLooper())) } 2 . StatusBar 높이 만큼 캡쳐 2 . StatusBar 영역의 Luminance 값 계산 fun calculateLuminance(bitmap: Bitmap): Int { val x = bitmap.width / 2 val y = bitmap.height / 2 val px = bitmap.getPixel(x, y) return (0.299 * Color.red(px) + 0.587 * Color.green(px) + 0.114 * Color.blue(px)).toInt() } 3. 캡쳐한 Bitmap이 Luminance 계산
  38. 1 . StatusBar 영역 만큼 캡쳐 한다. fun getStatusBarHeight(window: Window):

    Int { val decorView = window.decorView return decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이 fun captureStatusBarBitmap( window: Window, height: Int, onResult: (Bitmap?) -> Unit ) { val rect = Rect(0, 0, window.decorView.width, height) val bmp = createBitmap( width = rect.width(), height = rect.height() ) PixelCopy.request(window, rect, bmp, { _ -> onResult(bmp) }, Handler(Looper.getMainLooper())) } 2 . StatusBar 높이 만큼 캡쳐 2 . StatusBar 영역의 Luminance 값 계산 fun calculateLuminance(bitmap: Bitmap): Int { val x = bitmap.width / 2 val y = bitmap.height / 2 val px = bitmap.getPixel(x, y) return (0.299 * Color.red(px) + 0.587 * Color.green(px) + 0.114 * Color.blue(px)).toInt() } 3. 캡쳐한 Bitmap이 Luminance 계산 fun syncStatusBarAppearance(window: Window) { val height = getStatusBarHeight(window) captureStatusBarBitmap(window, height) { bmp -> bmp?.let { val lum = calculateLuminance(it) // 밝으면 다크 아이콘, // 어두우면 라이트 아이콘 WindowInsetsControllerCompat( window, window.decorView ).isAppearanceLightStatusBars = (lum > 150) } } } 4. 캡쳐한 Bitmap이 Luminance 계산
  39. 1 . StatusBar 영역 만큼 캡쳐 한다. fun getStatusBarHeight(window: Window):

    Int { val decorView = window.decorView return decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이 fun captureStatusBarBitmap( window: Window, height: Int, onResult: (Bitmap?) -> Unit ) { val rect = Rect(0, 0, window.decorView.width, height) val bmp = createBitmap( width = rect.width(), height = rect.height() ) PixelCopy.request(window, rect, bmp, { _ -> onResult(bmp) }, Handler(Looper.getMainLooper())) } 2 . StatusBar 높이 만큼 캡쳐 2 . StatusBar 영역의 Luminance 값 계산 fun calculateLuminance(bitmap: Bitmap): Int { val x = bitmap.width / 2 val y = bitmap.height / 2 val px = bitmap.getPixel(x, y) return (0.299 * Color.red(px) + 0.587 * Color.green(px) + 0.114 * Color.blue(px)).toInt() } 3. 캡쳐한 Bitmap이 Luminance 계산 3 . Luminance 값을 기반으로 색상 지정 fun syncStatusBarAppearance(window: Window) { val height = getStatusBarHeight(window) captureStatusBarBitmap(window, height) { bmp -> bmp?.let { val lum = calculateLuminance(it) // 밝으면 다크 아이콘, // 어두우면 라이트 아이콘 WindowInsetsControllerCompat( window, window.decorView ).isAppearanceLightStatusBars = (lum > 150) } } } 4. 캡쳐한 Bitmap이 Luminance 계산
  40. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } XML Window Inset
  41. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } 1. 적용하고자 하는 inset type을 갖고 온다. XML Window Inset
  42. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } 1. 적용하고자 하는 inset type을 갖고 온다. 2 . inset을 해당 View에 적용 한다. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } XML Window Inset
  43. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } 1. 적용하고자 하는 inset type을 갖고 온다. 2 . inset을 해당 View에 적용 한다. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } 3 . inset을 반환한다. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } XML Window Inset
  44. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } 1. 적용하고자 하는 inset type을 갖고 온다. 2 . inset을 해당 View에 적용 한다. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } 3 . inset을 반환한다. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } XML Window Inset
  45. public interface OnApplyWindowInsetsListener { /** * @return 소모(consumed)된 인셋을 제외한

    나머지 인셋 */ @NonNull WindowInsetsCompat onApplyWindowInsets( @NonNull View v, @NonNull WindowInsetsCompat insets ); } 소모된 Inset을 제외하고 적용한다. XML Window Inset
  46. public interface OnApplyWindowInsetsListener { /** * @return 소모(consumed)된 인셋을 제외한

    나머지 인셋 */ @NonNull WindowInsetsCompat onApplyWindowInsets( @NonNull View v, @NonNull WindowInsetsCompat insets ); } 소모(consumed)된 inset? XML Window Inset
  47. View의 Touch Event Flow랑 비슷하게 Consume을 시켜버린 Inset은 자식 View로

    내려가지 않는다. XML Window Inset https://developer.android.com/develop/ui/views/layout/edge-to-edge?hl=ko#backward-compatible-dispatching
  48. View의 Touch Event Flow랑 비슷하게 Consume을 시켜버린 Inset은 자식 View로

    내려가지 않는다. XML Window Inset https://developer.android.com/develop/ui/views/layout/edge-to-edge?hl=ko#backward-compatible-dispatching
  49. View의 Touch Event Flow랑 비슷하게 Consume을 시켜버린 Inset은 자식 View로

    내려가지 않는다. XML Window Inset https://developer.android.com/develop/ui/views/layout/edge-to-edge?hl=ko#backward-compatible-dispatching
  50. <?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/gray" android:orientation="vertical"> <View

    android:id="@+id/child" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/light_gray" /> </LinearLayout>
  51. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets }
  52. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets }
  53. ViewCompat.setOnApplyWindowInsetsListener( binding.child ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets }
  54. ViewCompat.setOnApplyWindowInsetsListener( binding.child ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets }
  55. ViewCompat.setOnApplyWindowInsetsListener( binding.child ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets }
  56. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) WindowInsetsCompat.CONSUMED }
  57. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) WindowInsetsCompat.CONSUMED }
  58. @Composable private fun Container( modi fi er: Modi fi er

    = Modi fi er ) { Box( modi fi er = modi fi er . fi llMaxSize() .systemBarsPadding() .background(colorResource(R.color.gray)) ) { Child() } } @Composable private fun Child( modi fi er: Modi fi er = Modi fi er ) { Box( modi fi er = modi fi er .systemBarsPadding() . fi llMaxWidth() .height(40.dp) .background(colorResource(R.color.light_gray)) ) } Compose Window Inset
  59. @Composable private fun Container( modi fi er: Modi fi er

    = Modi fi er ) { Box( modi fi er = modi fi er . fi llMaxSize() .systemBarsPadding() .background(colorResource(R.color.gray)) ) { Child() } } @Composable private fun Child( modi fi er: Modi fi er = Modi fi er ) { Box( modi fi er = modi fi er .systemBarsPadding() . fi llMaxWidth() .height(40.dp) .background(colorResource(R.color.light_gray)) ) } Compose Window Inset
  60. @Composable private fun Container( modi fi er: Modi fi er

    = Modi fi er ) { Box( modi fi er = modi fi er . fi llMaxSize() .systemBarsPadding() .background(colorResource(R.color.gray)) ) { Child() } } @Composable private fun Child( modi fi er: Modi fi er = Modi fi er ) { Box( modi fi er = modi fi er .systemBarsPadding() . fi llMaxWidth() .height(40.dp) .background(colorResource(R.color.light_gray)) ) } Compose Window Inset
  61. @Composable private fun Container( modi fi er: Modi fi er

    = Modi fi er ) { Box( modi fi er = modi fi er . fi llMaxSize() .systemBarsPadding() .background(colorResource(R.color.gray)) ) { Child() } } @Composable private fun Child( modi fi er: Modi fi er = Modi fi er ) { Box( modi fi er = modi fi er .systemBarsPadding() . fi llMaxWidth() .height(40.dp) .background(colorResource(R.color.light_gray)) ) } Compose Window Inset
  62. @Composable private fun Container( modi fi er: Modi fi er

    = Modi fi er ) { Box( modi fi er = modi fi er . fi llMaxSize() .systemBarsPadding() .background(colorResource(R.color.gray)) ) { Child() } } @Composable private fun Child( modi fi er: Modi fi er = Modi fi er ) { Box( modi fi er = modi fi er .systemBarsPadding() . fi llMaxWidth() .height(40.dp) .background(colorResource(R.color.light_gray)) ) } Compose Window Inset
  63. /** * Adds padding to accommodate the [system bars][WindowInsets.Companion.systemBars] insets.

    * * Any insets consumed by other insets padding modi fi ers or [consumeWindowInsets] on a parent layout * will be excluded from the padding. [WindowInsets.Companion.systemBars] will be * [consumed][consumeWindowInsets] for child layouts as well. * * For example, if a parent layout uses [statusBarsPadding], the * area that the parent layout pads for the status bars will not be padded again by this * [systemBarsPadding] modi fi er. * * When used, the [WindowInsets] will be consumed. * * @sample androidx.compose.foundation.layout.samples.systemBarsPaddingSample */ expect fun Modi fi er.systemBarsPadding(): Modi fi er
  64. /** * Adds padding to accommodate the [system bars][WindowInsets.Companion.systemBars] insets.

    * * Any insets consumed by other insets padding modi fi ers or [consumeWindowInsets] on a parent layout * will be excluded from the padding. [WindowInsets.Companion.systemBars] will be * [consumed][consumeWindowInsets] for child layouts as well. * * For example, if a parent layout uses [statusBarsPadding], the * area that the parent layout pads for the status bars will not be padded again by this * [systemBarsPadding] modi fi er. * * When used, the [WindowInsets] will be consumed. * * @sample androidx.compose.foundation.layout.samples.systemBarsPaddingSample */ expect fun Modi fi er.systemBarsPadding(): Modi fi er
  65. @Composable private fun Container( modi fi er: Modi fi er

    = Modi fi er ) { Box( modi fi er = modi fi er . fi llMaxSize() .padding(WindowInsets.systemBars.asPaddingValues()) .background(colorResource(R.color.gray)) ) { Child() } } @Composable private fun Child( modi fi er: Modi fi er = Modi fi er ) { Box( modi fi er = modi fi er .padding(WindowInsets.systemBars.asPaddingValues()) . fi llMaxWidth() .height(40.dp) .background(colorResource(R.color.light_gray)) ) }
  66. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) WindowInsetsCompat.CONSUMED } 1 .StatusBar랑 Cutout은 거의 겹쳐 있다. 2 . Cutout 은 hide/show 개념이 아니다.
  67. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) WindowInsetsCompat.CONSUMED } ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) WindowInsetsCompat.CONSUMED } 1 .StatusBar랑 Cutout은 거의 겹쳐 있다. 2 . Cutout 은 hide/show 개념이 아니다.
  68. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) WindowInsetsCompat.CONSUMED }
  69. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) WindowInsetsCompat.CONSUMED }
  70. val inset = WindowInsets.systemBarsIgnoringVisibility Column( modi fi er = Modi

    fi er . fi llMaxWidth() .windowInsetsPadding(inset), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "top inset: ${inset.getTop(LocalDensity.current)}", fontSize = 32.sp ) ShareUrlButton() }
  71. val inset = WindowInsets.systemBarsIgnoringVisibility Column( modi fi er = Modi

    fi er . fi llMaxWidth() .windowInsetsPadding(inset), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "top inset: ${inset.getTop(LocalDensity.current)}", fontSize = 32.sp ) ShareUrlButton() }
  72. val inset = WindowInsets.systemBarsIgnoringVisibility Column( modi fi er = Modi

    fi er . fi llMaxWidth() .windowInsetsPadding(inset), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "top inset: ${inset.getTop(LocalDensity.current)}", fontSize = 32.sp ) ShareUrlButton() } val inset = WindowInsets.systemBarsIgnoringVisibility Column( modi fi er = Modi fi er . fi llMaxWidth() .windowInsetsPadding(inset), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "top inset: ${inset.getTop(LocalDensity.current)}", fontSize = 32.sp ) ShareUrlButton() }
  73. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets }
  74. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets }
  75. ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars =

    insets.getInsets( WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets } ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { v, insets -> val sysBars = insets.getInsets( WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() ) v.updateMargin( top = sysBars.top, bottom = sysBars.bottom, start = v.marginStart, end = v.marginEnd ) insets }
  76. ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun

    onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } )
  77. ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun

    onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } )
  78. ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun

    onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } )
  79. ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun

    onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } )
  80. ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun

    onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } ) ViewCompat.setWindowInsetsAnimationCallback( binding.root, object : WindowInsetsAnimationCompat.Callback( DISPATCH_MODE_CONTINUE_ON_SUBTREE ) { override fun onProgress( insets: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat> ): WindowInsetsCompat { val types = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars() val insetsRect = insets.getInsets(types) binding.root.updateMargin( top = insetsRect.top, bottom = insetsRect.bottom, start = binding.root.marginStart, end = binding.root.marginEnd ) return insets } } )
  81. ModalBottomSheet hide할 때 NavigationBar 영역이 어색하다. 1 . Sheet가 hide

    된다. 2 . NavigationBar 영역에 뒤늦게 사라지는 View가 있다.
  82. ModalBottomSheet hide할 때 NavigationBar 영역이 어색하다. 1 . Sheet가 hide

    된다. 2 . NavigationBar 영역에 뒤늦게 사라지는 View가 있다.
  83. 어떤 View인가? 1 . ModalBottomSheet Dialog다. 2 . Dialog는 별도의

    Window를 갖고 있다. 3 . 즉 SystemBarBackground를 가질 수 있다.
  84. // androidx.compose.ui.window.DialogWrapper를 포크했습니다. private class ModalBottomSheetDialogWrapper( ... ) : ComponentDialog(

    ContextThemeWrapper( composeView.context, R.style.EdgeToEdgeFloatingDialogWindowTheme ) ) ModalBottomSheet ModalBottomSheet는 Dialog로 구현
  85. private class ModalBottomSheetDialogLayout( context: Context, override val window: Window, )

    : AbstractComposeView(context), DialogWindowProvider ModalBottomSheet 1 . DialogWindowProvider를 구현했다. 2 . window를 필수로 구현한다. 3 . window는 public 값이다.
  86. private class ModalBottomSheetDialogLayout( context: Context, override val window: Window, )

    : AbstractComposeView(context), DialogWindowProvider ModalBottomSheet 1 . DialogWindowProvider를 구현했다. 2 . window를 필수로 구현한다. 3 . window는 public 값이다. private class ModalBottomSheetDialogLayout( context: Context, override val window: Window, ) : AbstractComposeView(context), DialogWindowProvider
  87. private class ModalBottomSheetDialogLayout( context: Context, override val window: Window, )

    : AbstractComposeView(context), DialogWindowProvider ModalBottomSheet 1 . DialogWindowProvider를 구현했다. 2 . window를 필수로 구현한다. 3 . window는 public 값이다. private class ModalBottomSheetDialogLayout( context: Context, override val window: Window, ) : AbstractComposeView(context), DialogWindowProvider interface DialogWindowProvider { val window: Window }
  88. ModalBottomSheet( sheetState = sheetState, onDismissRequest = { scope.launch { sheetState.hide()

    } }, ) { val window = (LocalView.current.parent as DialogWindowProvider).window if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } // contents }
  89. ModalBottomSheet( sheetState = sheetState, onDismissRequest = { scope.launch { sheetState.hide()

    } }, ) { val window = (LocalView.current.parent as DialogWindowProvider).window if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } // contents }
  90. ModalBottomSheet( sheetState = sheetState, onDismissRequest = { scope.launch { sheetState.hide()

    } }, ) { val window = (LocalView.current.parent as DialogWindowProvider).window if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } // contents } ModalBottomSheet( sheetState = sheetState, onDismissRequest = { scope.launch { sheetState.hide() } }, ) { val window = (LocalView.current.parent as DialogWindowProvider).window if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } // contents }
  91. ModalBottomSheet( sheetState = sheetState, onDismissRequest = { scope.launch { sheetState.hide()

    } }, ) { val window = (LocalView.current.parent as DialogWindowProvider).window if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } // contents } ModalBottomSheet( sheetState = sheetState, onDismissRequest = { scope.launch { sheetState.hide() } }, ) { val window = (LocalView.current.parent as DialogWindowProvider).window if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } // contents } ModalBottomSheet( sheetState = sheetState, onDismissRequest = { scope.launch { sheetState.hide() } }, ) { val window = (LocalView.current.parent as DialogWindowProvider).window if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } // contents }
  92. 참고 자료 1 .https://developer.android.com/develop/ui/views/layout/edge-to-edge?hl=ko 2 . https://developer.android.com/codelabs/edge-to-edge?hl=ko# 2 3 .

    https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/ core/java/com/android/internal/policy/ DecorView.java;l= 1 4 0 9 ;drc= 6 1 1 9 7 3 6 4 3 6 7 c 9 e 4 0 4 c 7 da 6 9 0 0 6 5 8 f 1 b 1 6 c 4 2 d 0 da