先了解必须知道的东西
Android 10+ 提供了深色模式的系统开关, SDK 兼容低版本可以在 App 设置开关
Android 10+ 深色模式 会优先使用 valus-night
下的资源
设置深色模式的代码,可单个页面设置,可全局设置,全局设置后下一次冷启动打开还是会默认跟随系统,需要做持久化处理
强制深色模式 可以忽略你硬编码设置的色值,即使写死了颜色值是黑色,也能显示白色。强制深色模式的目的就是要黑底白字,深色底浅色字的色值不会强制改变
低版本系统不会根据状态栏颜色自动改变状态栏字体颜色
针对以上,接下来需要做的:
浅色 浅色底深色字 value/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="background_purity">#FFFFFFFF</color>
<color name="primaryText">#333333</color>
<color name="secondaryText">#8a000000</color>
</resources>
深色 深色底浅色字 valus-night/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="background_purity">#FF000000</color>
<color name="primaryText">#ffffffff</color>
<color name="secondaryText">#b3ffffff</color>
</resources>
当然,主题 themes.xml
也可以做两套,而且现在的 MD3 就是用主题实现默认的深色模式
AppCompat 下的主题 都有 Light 和 DayNight 两套对应
value/themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#FF6200EE</item>
</style>
</resources>
value-night/themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="colorPrimary">#FFBB86FC</item>
</style>
</resources>
要添加其它颜色或主题,直接加就行了
API
// 当前页面
delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES
// App 全局设置 (App 退出后会失效)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
持久化的设置
private fun initView() {
// set text
initViewLightModel()
// set click
binding.llNightModel.onClick {
val currentMode = binding.tvNightModel.text
val nightModeArray = arrayOf("跟随系统", "始终关闭", "始终开启")
val index = try {
nightModeArray.toList().indexOf(currentMode)
} catch (e: Exception) {
0
}
var selectModel = SPUtils.getInstance().getInt("night_model")
AlertDialog.Builder(this)
.setTitle("深色模式")
.setSingleChoiceItems(nightModeArray, index) { dialog, which ->
// which 即 index
selectModel = when (which) {
// 对应 nightModeArray 的索引
1 -> {
AppCompatDelegate.MODE_NIGHT_NO
}
2 -> {
AppCompatDelegate.MODE_NIGHT_YES
}
else -> {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
}
}
.setNegativeButton("取消", null)
.setPositiveButton("确定") { dialog, which ->
SPUtils.getInstance().put("night_model", selectModel)
// set text
initViewLightModel()
// set model
AppCompatDelegate.setDefaultNightMode(selectModel)
}
.show()
}
}
private fun initViewLightModel() {
binding.tvNightModel.text = when (SPUtils.getInstance().getInt("night_model")) {
AppCompatDelegate.MODE_NIGHT_NO -> {
"始终关闭"
}
AppCompatDelegate.MODE_NIGHT_YES -> {
"始终开启"
}
else -> {
"跟随系统"
}
}
}
App 启动时 取出持久化值进行初始化设置
class App : Application() {
override fun onCreate() {
super.onCreate()
initNightModel()
}
private fun initNightModel() {
val lightModel = SPUtils.getInstance().getInt("night_model")
AppCompatDelegate.setDefaultNightMode(lightModel)
}
}
完成后,基本实现了深色模式的开关设置
但是当你的页面是不是深色背景浅色字体时,打开深色模式系统开关 VS 关闭深色模式系统开关且打开App深色模式开关, 你会发现两个都是深色模式,但是显示不一致。
是因为你的深色模式对应色值不符合标准(深色底浅色字),系统深色模式打开时, App启用了强制深色模式 forceDarkAllowed
而且,此时在系统深色模式开启时APP设置始终关闭深色模式是无效的
可以直接在 布局 android:forceDarkAllowed="false"
或 代码 view.isForceDarkAllowed = false
设置,
在主题设置也行
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="colorPrimary">#FFBB86FC</item>
<!--
false, 不允许强制深色模式
系统深色模式 关闭时,APP 深色模式 不会强制适配
系统深色模式 开启时,APP 普通模式 不会强制适配
硬编码色值不会被强制改变
避免自动适配的效果不合预期
即开启自定义适配 手动适配
-->
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
</style>
建议: 如果App提供深色主题设置的开关, 可以直接在基类主题禁用强制深色模式, 再在特定页面或View打开 (不是我不适配, 只是设计没有提供对应深色模式的设计图), 因为强制深色模式的效果可能不是你想象中的样子, 只是大致的自动适配
到此就差不多完成了,但是当你拿出你的钉子户小米6时,发现还是有点不对劲。哦!原理是状态栏字体颜色不会跟随改变
一般情况, 10+ 系统的状态栏字体颜色会自动根据状态栏背景色去变化
但是低版本的 只能手动去改变
我们可以根据深色模式设置状态栏字体颜色
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 状态栏导航栏颜色
immersionBar {
statusBarColor(initStatusBarColor())
navigationBarColor(initNavBarColor())
// 状态栏字体颜色 (系统自动变色优先, 即对低版本系统不会自动变色有效)
statusBarDarkFont(initStatusBarDarkFont())
}
// ...
}
protected open fun initStatusBarDarkFont(): Boolean {
// 非深色模式都默认设置黑色字体
val lightModel = SPUtils.getInstance().getInt("night_model")
return lightModel != AppCompatDelegate.MODE_NIGHT_YES
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
immersionBar {
// 低版本系统状态栏字体不会自动变色
statusBarDarkFont(initStatusBarDarkFont())
}
}
作用域是当前的 Window,所以可以在 BaseActivity 和 弹窗的基类去设置
再附上获取当前模式的方法
/**
* 获取 System 暗色模式 开关状态
*/
val Context.sysIsDarkMode: Boolean
get() {
val uiModeManager = ContextCompat.getSystemService(this, UiModeManager::class.java)
?: return false
return uiModeManager.nightMode == UiModeManager.MODE_NIGHT_YES
}
/**
* 获取 APP 最终是否 暗色模式
*/
val Context.appIsDarkMode: Boolean
get() {
return (this.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
}
以上用到了一些第三方库 AndroidUtilCode
ImmersionBar
打完收工。