Share
Embed
DroidKnights 2025 - 나도 edgeToEdge 적용하기 싫어. 근데 누군가는 해야 하잖아?
targetSdk35의 가장 큰 변경사항인 EdgeToEdge에 대해서 알아보았습니다.
ezhoon
June 17, 2025
Transcript
148 slides
-
나 도 edgeToEdge 저 ᆨ 요 ᆼ 하 기 시
ᆶ 어 . 근 데 누 구 ᆫ 가 는 해 야 하 자 ᆭ 아 ? 이지훈 SOOP -
목차 1 EdgeToEdge 톺아보자 2 EdgeToEdge 적용기 3 Inset은 어떻게
다뤄야 하는가? 4 적용하면서 생겼던 이슈들 -
fun ComponentActivity.enableEdgeToEdge( statusBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = Color.TRANSPARENT, darkScrim
= Color.TRANSPARENT ), navigationBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = DefaultLightScrim, darkScrim = DefaultDarkScrim ) ) { // EdgeToEdge 적용 부분 } -
fun ComponentActivity.enableEdgeToEdge( statusBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = Color.TRANSPARENT, darkScrim
= Color.TRANSPARENT ), navigationBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = DefaultLightScrim, darkScrim = DefaultDarkScrim ) ) { // EdgeToEdge 적용 부분 } -
fun ComponentActivity.enableEdgeToEdge( statusBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = Color.TRANSPARENT, darkScrim
= Color.TRANSPARENT ), navigationBarStyle: SystemBarStyle = SystemBarStyle.auto( lightScrim = DefaultLightScrim, darkScrim = DefaultDarkScrim ) ) { // EdgeToEdge 적용 부분 } -
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto(
lightScrim = Color.TRANSPARENT, darkScrim = Color.TRANSPARENT, ), ) setContentView(binding.root) } -
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto(
lightScrim = Color.TRANSPARENT, darkScrim = Color.TRANSPARENT, ), ) setContentView(binding.root) } -
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto(
lightScrim = Color.TRANSPARENT, darkScrim = Color.TRANSPARENT, ), ) setContentView(binding.root) } -
@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 } -
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 ) } -
fun dark(@ColorInt scrim: Int): SystemBarStyle { return SystemBarStyle( lightScrim =
scrim, darkScrim = scrim, nightMode = UiModeManager.MODE_NIGHT_YES, detectDarkMode = { _ -> true } ) } -
fun light( @ColorInt scrim: Int, @ColorInt darkScrim: Int ): SystemBarStyle
{ return SystemBarStyle( lightScrim = scrim, darkScrim = darkScrim, nightMode = UiModeManager.MODE_NIGHT_NO, detectDarkMode = { _ -> false } ) } -
fun light( @ColorInt scrim: Int, @ColorInt darkScrim: Int ): SystemBarStyle
{ return SystemBarStyle( lightScrim = scrim, darkScrim = darkScrim, nightMode = UiModeManager.MODE_NIGHT_NO, detectDarkMode = { _ -> false } ) } -
/** * 내비게이션 바에 완전히 투명한 배경이 요청되었을 때, *
시스템이 충분한 대비를 보장해야 하는지를 설정합니다. * 이 값으로 설정하면, 시스템은 이 앱의 콘텐츠와 내비게이션 바 간에 충분한 대비가 필요한지를 판단하여, * 대비를 확보하기 위해 스크림이 필요한지 여부를 결정하고, 적절한 내비게이션 바 배경색을 설정합니다. * 내비게이션 바 색상이 불투명하거나 반투명이면, 이 속성의 값은 아무런 효과를 미치지 않습니다. */ fun setNavigationBarContrastEnforced(enforceContrast: Boolean) { } -
/** * 내비게이션 바에 완전히 투명한 배경이 요청되었을 때, *
시스템이 충분한 대비를 보장해야 하는지를 설정합니다. * 이 값으로 설정하면, 시스템은 이 앱의 콘텐츠와 내비게이션 바 간에 충분한 대비가 필요한지를 판단하여, * 대비를 확보하기 위해 스크림이 필요한지 여부를 결정하고, 적절한 내비게이션 바 배경색을 설정합니다. * 내비게이션 바 색상이 불투명하거나 반투명이면, 이 속성의 값은 아무런 효과를 미치지 않습니다. */ fun setNavigationBarContrastEnforced(enforceContrast: Boolean) { } -
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) } -
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) } -
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 ) -
private class EdgeToEdgeApi21 : 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 세팅 코드 } } -
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 세팅 코드 } } -
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 세팅 코드 } } -
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 세팅 코드 } } -
DecorView는 1 . LinearLayout 2 . navigationBarBackground 3 . statusBackground
로 이루어져 있다. DecorView 부터 -
DecorView는 1 . LinearLayout 2 . navigationBarBackground 3 . statusBackground
로 이루어져 있다. DecorView 부터 -
DecorView는 1 . LinearLayout 2 . navigationBarBackground 3 . statusBackground
로 이루어져 있다. DecorView 부터 -
DecorView는 1 . LinearLayout 2 . navigationBarBackground 3 . statusBackground
로 이루어져 있다. DecorView 부터 -
DecorView는 1 . LinearLayout 2 . navigationBarBackground 3 . statusBackground
로 이루어져 있다. DecorView 부터 -
EdgeToEdge 적용 전 1 . StatusBar 색상을 직접 제어하고 있다.
2 . NavigationBar 색상의 이질감이 있다. -
EdgeToEdge 적용 전 1 . StatusBar 색상을 직접 제어하고 있다.
2 . NavigationBar 색상의 이질감이 있다. -
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) } -
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) } -
EdgeToEdge 적용 후 1 . StatusBar 색상을 제어하고 있지 않다.
2 . NavigationBar 색상을 맞닿은 View와 동일한 색상 -
EdgeToEdge 적용 후 1 . StatusBar 색상을 제어하고 있지 않다.
2 . NavigationBar 색상을 맞닿은 View와 동일한 색상 -
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 -> 밝은 색상 -
fun getStatusBarHeight(window: Window): Int { val decorView = window.decorView return
decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이 -
fun getStatusBarHeight(window: Window): Int { val decorView = window.decorView return
decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 1 . StatusBar 높이 -
fun getStatusBarHeight(window: Window): Int { val decorView = window.decorView return
decorView.rootWindowInsets.getInsets( WindowInsets.Type.statusBars() ).top } 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 높이 만큼 캡쳐 -
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 높이 만큼 캡쳐 -
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 계산 -
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 계산 -
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 계산 -
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 -
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 -
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 -
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 -
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 -
public interface OnApplyWindowInsetsListener { /** * @return 소모(consumed)된 인셋을 제외한
나머지 인셋 */ @NonNull WindowInsetsCompat onApplyWindowInsets( @NonNull View v, @NonNull WindowInsetsCompat insets ); } 소모된 Inset을 제외하고 적용한다. XML Window Inset -
public interface OnApplyWindowInsetsListener { /** * @return 소모(consumed)된 인셋을 제외한
나머지 인셋 */ @NonNull WindowInsetsCompat onApplyWindowInsets( @NonNull View v, @NonNull WindowInsetsCompat insets ); } 소모(consumed)된 inset? XML Window Inset -
View의 TouchEvent를 예시로 dispatch 된 touch는 하위 View로 내려가지 않는다.
XML Window Inset -
View의 TouchEvent를 예시로 dispatch 된 touch는 하위 View로 내려가지 않는다.
XML Window Inset -
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 -
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 -
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 -
<?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> -
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 } -
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 } -
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 } -
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 } -
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 } -
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 } -
@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 -
@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 -
@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 -
@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 -
@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 -
/** * 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 -
/** * 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 -
@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)) ) } -
공유하기를 하면 덜컥거린다. 1 . 공유하기 시트가 뜨면 StatusBar는 사라진다.
2 . StatusBar가 사라져도 위치는 유지하고 싶다. -
공유하기를 하면 덜컥거린다. 1 . 공유하기 시트가 뜨면 StatusBar는 사라진다.
2 . StatusBar가 사라져도 위치는 유지하고 싶다. -
1 .StatusBar랑 Cutout은 거의 겹쳐 있다. 2 . Cutout 은
hide/show 개념이 아니다. -
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 개념이 아니다. -
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 개념이 아니다. -
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 } -
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() } -
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() } -
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 } -
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 } -
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 } } ) 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 } } ) -
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 } } ) -
ModalBottomSheet hide할 때 NavigationBar 영역이 어색하다. 1 . Sheet가 hide
된다. 2 . NavigationBar 영역에 뒤늦게 사라지는 View가 있다. -
ModalBottomSheet hide할 때 NavigationBar 영역이 어색하다. 1 . Sheet가 hide
된다. 2 . NavigationBar 영역에 뒤늦게 사라지는 View가 있다. -
어떤 View인가? 1 . ModalBottomSheet Dialog다. 2 . Dialog는 별도의
Window를 갖고 있다. 3 . 즉 SystemBarBackground를 가질 수 있다. -
// androidx.compose.ui.window.DialogWrapper를 포크했습니다. private class ModalBottomSheetDialogWrapper( ... ) : ComponentDialog(
ContextThemeWrapper( composeView.context, R.style.EdgeToEdgeFloatingDialogWindowTheme ) ) ModalBottomSheet ModalBottomSheet는 Dialog로 구현 -
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 ModalBottomSheet 1 . DialogWindowProvider를 구현했다. 2 . window를 필수로 구현한다. 3 . window는 public 값이다. private class ModalBottomSheetDialogLayout( context: Context, override val window: Window, ) : AbstractComposeView(context), DialogWindowProvider -
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 } -
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 } 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 } 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 } -
참고 자료 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