Почему на 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))
}

Ответы (0 шт):