关于导航栏颜色透明这件事

关于需求

产品希望导航栏也做沉浸,变成透明,而不是在亮色时会有一个半透明的白色遮罩。

关于代码

ini 复制代码
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        combine(
            viewModel.appThemeConfig,
            isSystemInDarkTheme()
        ) { appThemeConfig, isDarkTheme ->
            when (appThemeConfig) {
                AppThemeConfig.FOLLOW_SYSTEM -> isDarkTheme
                AppThemeConfig.LIGHT -> false
                AppThemeConfig.DARK -> true
            }
        }.distinctUntilChanged()
            .collect { isDarkTheme ->
                this@MainActivity.isDarkTheme.value = isDarkTheme
                // 设置导航栏和状态栏颜色
                enableEdgeToEdge(
                    statusBarStyle = SystemBarStyle.auto(
                        lightScrim = android.graphics.Color.TRANSPARENT,
                        darkScrim = android.graphics.Color.TRANSPARENT,
                    ) { isDarkTheme },
                    navigationBarStyle = SystemBarStyle.auto(
                        lightScrim = android.graphics.Color.TRANSPARENT,
                        darkScrim = android.graphics.Color.TRANSPARENT,
                    ) { isDarkTheme },
                )

            }
    }
}

通过官方的enableEdgeToEdge方法,设置了状态栏颜色,将导航栏和状态栏都改成了透明的。

但是!!居然没有生效。

关于问题

只能AI了一下,AI告诉我,这个地方设置导航栏透明是没有用的,因为官方考虑到三按钮看不清的问题,添加了强对比的逻辑,那块半透明的白色遮罩就是系统强对比模式下所添加的。

既然如此,按照AI的逻辑,我们添加一下就可以了。

ini 复制代码
combine(
    viewModel.appThemeConfig,
    isSystemInDarkTheme()
) { appThemeConfig, isDarkTheme ->
    when (appThemeConfig) {
        AppThemeConfig.FOLLOW_SYSTEM -> isDarkTheme
        AppThemeConfig.LIGHT -> false
        AppThemeConfig.DARK -> true
    }
}.distinctUntilChanged()
    .collect { isDarkTheme ->
        this@MainActivity.isDarkTheme.value = isDarkTheme
        // 设置导航栏和状态栏颜色
        enableEdgeToEdge(
            statusBarStyle = SystemBarStyle.auto(
                lightScrim = android.graphics.Color.TRANSPARENT,
                darkScrim = android.graphics.Color.TRANSPARENT,
            ) { isDarkTheme },
            navigationBarStyle = SystemBarStyle.auto(
                lightScrim = android.graphics.Color.TRANSPARENT,
                darkScrim = android.graphics.Color.TRANSPARENT,
            ) { isDarkTheme },
        )
        // 强制系统关闭对比模式
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            window.isNavigationBarContrastEnforced = false
        }
    }

但是!!依旧是没有生效!!!

这下不得不看下源码了

scss 复制代码
public fun ComponentActivity.enableEdgeToEdge(
    statusBarStyle: SystemBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT),
    navigationBarStyle: SystemBarStyle = SystemBarStyle.auto(DefaultLightScrim, DefaultDarkScrim),
) {
    val view = window.decorView
    val impl =
        Impl
            ?: (if (Build.VERSION.SDK_INT >= 35) {
                    EdgeToEdgeApi35()
                } else if (Build.VERSION.SDK_INT >= 30) {
                    EdgeToEdgeApi30()
                } else if (Build.VERSION.SDK_INT >= 29) {
                    EdgeToEdgeApi29()
                } else if (Build.VERSION.SDK_INT >= 28) {
                    EdgeToEdgeApi28()
                } else if (Build.VERSION.SDK_INT >= 26) {
                    EdgeToEdgeApi26()
                } else {
                    EdgeToEdgeApi23()
                })
                .also { Impl = it }
    val setup = Runnable {
        impl.setUp(
            statusBarStyle,
            navigationBarStyle,
            window,
            view,
            statusBarStyle.detectDarkMode(view.resources),
            navigationBarStyle.detectDarkMode(view.resources),
        )
    }
    (view as ViewGroup).apply {
        if (!children.any { it.tag is EdgeToEdgeImpl }) {
            // Adds a view to listen to configuration changes.
            addView(
                object : View(view.context) {
                        override fun onConfigurationChanged(newConfig: Configuration) {
                            setup.run()
                        }
                    }
                    .apply {
                        tag = impl
                        visibility = View.GONE
                        setWillNotDraw(true)
                    }
            )
        }
    }
    setup.run()
    impl.adjustLayoutInDisplayCutoutMode(window)
}

简单讲下,这个enableEdgeToEdge根据不同的sdk设置的方法不同,同时添加了个View进行configurationChange的监听(这一步操作妙~啊),然后执行了这个设置方法。

那我们用的sdk35具体做了什么呢?

kotlin 复制代码
@RequiresApi(35)
private class EdgeToEdgeApi35 : EdgeToEdgeApi30() {

    @Suppress("DEPRECATION")
    @DoNotInline
    override fun setUp(
        statusBarStyle: SystemBarStyle,
        navigationBarStyle: SystemBarStyle,
        window: Window,
        view: View,
        statusBarIsDark: Boolean,
        navigationBarIsDark: Boolean,
    ) {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        window.statusBarColor = Color.TRANSPARENT
        window.navigationBarColor = Color.TRANSPARENT
        val statusBarColor = statusBarStyle.getScrimWithEnforcedContrast(statusBarIsDark)
        val navBarColor = navigationBarStyle.getScrimWithEnforcedContrast(navigationBarIsDark)
        (view as? ViewGroup)?.apply {
            if (
                !children.any {
                    it.tag.run {
                        if (this is List<*> && size == 4 && get(0) is ColorProtection) {
                            forEach { protection ->
                                (protection as? ColorProtection)?.apply {
                                    when (protection.side) {
                                        TOP -> protection.color = statusBarColor
                                        LEFT -> protection.color = navBarColor
                                        RIGHT -> protection.color = navBarColor
                                        BOTTOM -> protection.color = navBarColor
                                    }
                                }
                            }
                            true
                        } else {
                            false
                        }
                    }
                }
            ) {
                // A child view with the tag, a list of 4 ColorProtections, doesn't exist.
                // Let's add the protections if any protection is not transparent.
                if (statusBarColor != Color.TRANSPARENT || navBarColor != Color.TRANSPARENT) {
                    val protections =
                        listOf(
                            ColorProtection(TOP, statusBarColor),
                            ColorProtection(LEFT, navBarColor),
                            ColorProtection(RIGHT, navBarColor),
                            ColorProtection(BOTTOM, navBarColor),
                        )
                    addView(ProtectionLayout(view.context, protections).apply { tag = protections })
                }
            }
        }
        window.isNavigationBarContrastEnforced =
            navigationBarStyle.nightMode == UiModeManager.MODE_NIGHT_AUTO
        WindowInsetsControllerCompat(window, view).run {
            isAppearanceLightStatusBars = !statusBarIsDark
            isAppearanceLightNavigationBars = !navigationBarIsDark
        }
    }
}

也有点长了,简单来说就是设置了状态栏和导航栏的颜色,然后设置了对比模式,设置了状态栏和导航栏文本的颜色。

其中最关键的就是

ini 复制代码
window.isNavigationBarContrastEnforced =
            navigationBarStyle.nightMode == UiModeManager.MODE_NIGHT_AUTO

它的意思是,我们如果开启了自动亮暗色的模式,就会强制开启导航栏强对比的模式。

关于原因

至此,真相大白,因为添加了亮暗色切换的逻辑,官方怕开启自动切换后,导航栏三按钮看不见的情况,于是又添加了强对比逻辑,在这种自动切换亮暗色的情况下,给你加个半透明的白色遮罩。

而我直接在enableEdgeToEdge之后设置isNavigationBarContrastEnforced = false,肯定是没有用了,因为它会在onConfigurationChanged又设置为true,直接改显然没有照顾到所有情况。

太体贴了,不愧是官方,不愧是官方,但是,我咋改呢,官方的代码我没法直接动。

关于解决

没办法了,现学现用了,它加view监听,我也加view监听。

这边插一下,我特意查询了一下onConfigurationChanged的传递逻辑,总结一下:

  1. 先Activity,后View
  2. View之间根据节点排序

那就没问题了,我后加的view,也会后触发onConfigurationChanged

ini 复制代码
// 设置导航栏和状态栏颜色
enableEdgeToEdge(
    statusBarStyle = SystemBarStyle.auto(
        lightScrim = android.graphics.Color.TRANSPARENT,
        darkScrim = android.graphics.Color.TRANSPARENT,
    ) { isDarkTheme },
    navigationBarStyle = SystemBarStyle.auto(
        lightScrim = android.graphics.Color.TRANSPARENT,
        darkScrim = android.graphics.Color.TRANSPARENT,
    ) { isDarkTheme },
)
// 使用View监听onConfigurationChanged
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    (window.decorView as ViewGroup).run {
        if (!children.any { it.tag == "force" }) {
            addView( object : View(context) {
                override fun onConfigurationChanged(newConfig: Configuration?) {
                    super.onConfigurationChanged(newConfig)
                    window.isNavigationBarContrastEnforced = false
                }
            }.apply {
                tag = "force"
                visibility = View.GONE
                setWillNotDraw(true)
            })
        }
    }
}
// 直接执行
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    window.isNavigationBarContrastEnforced = false
}

OK 运行一下看看

顺利搞定!!

官方文档地址:developer.android.com/develop/ui/...

相关推荐
默_笙1 小时前
🚅 地铁的"回头路、存档点与人工闸机"(下):LangGraph 的循环、持久化与中断
前端·javascript
ClouGence1 小时前
Chrome Recorder 能用于长期回归测试吗?
前端·chrome·测试
拖孩1 小时前
这个小程序是 AI 帮我写的,可它里面一个 AI 功能都没有
前端·后端·微信小程序
Nayana2 小时前
《Web 到 HarmonyOS》-- 业务分析:消息推送业务方法
前端
GreenTea2 小时前
发布 3 天登顶 HN:不生成一个字的模型 Jev,我把它的源码和黑料都扒了一遍
前端·后端·算法
前端小崔2 小时前
Three.js 与 Cesium 融合实战问题汇总
前端·three.js·cesium
wizardpisces3 小时前
Claude Code 是 Angular,Codex 是 Vue,DeepSeek 想当 React
前端·人工智能
计算机魔术师3 小时前
纽约时报诉 OpenAI 案新解封文件:微软与 OpenAI 内部承认 LLM 建立在窃取之上并引发 Doom Loop
前端
PedroQue993 小时前
uni-app x 事件通信插件重磅上线
前端·uni-app