Почему на Android 15 decoreView применяет дополнительные отступы после переключения между темами?
Подготавливаю приложение к Android 15, включая поддержку edge2edge. На версиях Android < 15 все работает отлично. Использую следующие экстеншны
fun ComponentActivity.enableEdgeToEdgeMode() {
if (is35orMore()) {
window.isNavigationBarContrastEnforced = true
} else {
enableEdgeModeToEdgeForLegacy()
}
}
private fun ComponentActivity.enableEdgeModeToEdgeForLegacy() {
val bottomNavigationColor = if (isGestureNavigationModeInLegacy()) {
resources.getColor(R.color.color_bg_transparent_default, null)
} else {
resources.getColor(R.color.color_bg_primary_normal, null)
}
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.light(
scrim = Color.TRANSPARENT,
darkScrim = Color.TRANSPARENT
),
navigationBarStyle = SystemBarStyle.light(
scrim = bottomNavigationColor,
darkScrim = bottomNavigationColor
)
)
Но на Android 15 после переключения темы (в любом порядке) Activity пересоздается и window.decorView применяет дополнительные отступы сверху. Поведение похоже на set fitsystemWindows = true для корневого ViewGroup. Большой отступ вверху экрана, высота window.decorView была уменьшена, status bar стал белым в белый цвет. Есть у кого-нибудь идеи, как это исправить? Это воспроизводится только для Android Api == 35.
Вот дополнительные экстеншны которые я применяю для включения инсетов на фрагментах:
private fun ComponentActivity.isGestureNavigationModeInLegacy() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val navigationMode = Settings.Secure.getInt(contentResolver, "navigation_mode", 0)
navigationMode == 2 // 2 — это режим жестов
} else {
false
}
fun Activity.setupStatusBarIndicatorsColor(isLightStatusBar: Boolean) {
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = !isLightStatusBar && isLightMode
}
enum class InsetSide {
LEFT, RIGHT, TOP, BOTTOM;
}
enum class InsetType {
MARGIN, PADDING
}
private fun recordInitialInsets(view: View, insetType: InsetType): Rect {
return when (insetType) {
MARGIN -> Rect(view.marginLeft, view.marginTop, view.marginRight, view.marginBottom)
PADDING -> Rect(view.paddingLeft, view.paddingTop, view.paddingRight, view.paddingBottom)
}
}
/**
* @param targetView View к которой будут применяться инсеты.
* @param insetSide Сторона View к которой будут применяться инсеты
* @param insetType Тип применения инсета. Margin для Toolbar/ActionBar и Padding для RecyclerView в сочетании с clipToPadding = false.
* @param consumeInsets Отвечает за распространение инсетов по иерархии дерева View. По умолчанию true и
* инсеты не распространяются. Это улучшает производительность, но отменяет возможность получить инсеты в других вью дерева.
* Если на экране требуется применить несколько видов инсетов (например статус бар и клавиатура) - то необходимо выставлять этот параметр как false во всех методах
* в порядке их вызова, кроме последнего.
*/
fun applySystemBarsInsets(targetView: View, insetSide: InsetSide, insetType: InsetType = MARGIN, consumeInsets: Boolean = true) {
val initialInsets = recordInitialInsets(targetView, insetType)
ViewCompat.setOnApplyWindowInsetsListener(targetView) { view, windowInsets ->
val systemBarInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
when (insetType) {
MARGIN -> updateMargins(view, insetSide, systemBarInsets, initialInsets)
PADDING -> updatePaddings(view, insetSide, systemBarInsets, initialInsets)
}
if (consumeInsets) WindowInsetsCompat.CONSUMED else windowInsets
}
}
private fun updateMargins(view: View, insetSide: InsetSide, systemBarInsets: Insets, viewInitialsMargin: Rect) {
view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
when (insetSide) {
InsetSide.TOP -> topMargin = systemBarInsets.top + viewInitialsMargin.top
InsetSide.BOTTOM -> bottomMargin = systemBarInsets.bottom + viewInitialsMargin.bottom
InsetSide.LEFT -> rightMargin = systemBarInsets.right + viewInitialsMargin.right
InsetSide.RIGHT -> leftMargin = systemBarInsets.left + viewInitialsMargin.left
}
}
}
private fun updatePaddings(view: View, insetSide: InsetSide, systemBarInsets: Insets, viewInitialsMargin: Rect) {
when (insetSide) {
InsetSide.TOP -> view.updatePadding(top = systemBarInsets.top + viewInitialsMargin.top)
InsetSide.BOTTOM -> view.updatePadding(bottom = systemBarInsets.bottom + viewInitialsMargin.bottom)
InsetSide.LEFT -> view.updatePadding(left = systemBarInsets.left + viewInitialsMargin.left)
InsetSide.RIGHT -> view.updatePadding(right = systemBarInsets.right + viewInitialsMargin.right)
}
}
/**
* @param targetView View к которой будут применяться инсеты.
* @param insetType Тип применения инсета.
* @param consumeInsets Отвечает за распространение инсетов по иерархии дерева View. По умолчанию true и
* инсеты не распространяются. Это улучшает производительность, но отменяет возможность получить инсеты в других вью дерева.
* Если на экране требуется применить несколько видов инсетов (например статус бар и клавиатура) - то необходимо выставлять этот параметр как false во всех методах
* в порядке их вызова, кроме последнего.
* @param addBottomInset Учитывать отступ снизу у system bottom bar. Если присутствует system bottom bar - то передаем false.
*/
fun applyKeyboardInsets(targetView: View, insetType: InsetType = MARGIN, consumeInsets: Boolean = true, addBottomInset: Boolean = true) {
val viewInitialInsets = recordInitialInsets(targetView, insetType)
ViewCompat.setOnApplyWindowInsetsListener(targetView) { view, windowInsets ->
val keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
val systemBarInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val resultInset = if (keyboardInsets.bottom == 0 && addBottomInset) systemBarInsets.bottom else keyboardInsets.bottom
when (insetType) {
MARGIN -> view.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = resultInset + viewInitialInsets.bottom }
PADDING -> view.updatePadding(bottom = resultInset + viewInitialInsets.bottom)
}
if (consumeInsets) WindowInsetsCompat.CONSUMED else windowInsets
}
}
@Composable
fun KeyboardSpacer(additionalContentInset: Dp = 0.dp) {
val isKeyboardOpen = WindowInsets.ime.asPaddingValues().calculateBottomPadding() != 0.dp
if (isKeyboardOpen) {
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.ime))
} else {
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}
Spacer(Modifier.height(additionalContentInset))
}