深色模式
问题
Android 深色模式(Dark Theme)如何实现?如何适配资源和颜色?
答案
1. 启用 DayNight 主题
<!-- 使用 DayNight 主题自动支持深色模式 -->
<style name="Theme.MyApp" parent="Theme.Material3.DayNight.NoActionBar">
<!-- 通用属性 -->
</style>
<!-- res/values/colors.xml - 浅色模式 -->
<color name="md_theme_surface">#FFFBFE</color>
<color name="md_theme_on_surface">#1C1B1F</color>
<!-- res/values-night/colors.xml - 深色模式 -->
<color name="md_theme_surface">#1C1B1F</color>
<color name="md_theme_on_surface">#E6E1E5</color>
2. 代码中切换深色模式
// 跟随系统设置(推荐)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
// 强制深色
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
// 强制浅色
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
// 判断当前是否深色模式
val isDarkMode = resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
3. 资源适配
| 资源目录 | 说明 |
|---|---|
values-night/colors.xml | 深色模式颜色 |
values-night/themes.xml | 深色模式主题覆盖 |
drawable-night/ | 深色模式图片 |
最佳实践
- 使用
?attr/colorOnSurface等主题属性替代硬编码颜色 - 避免纯白(
#FFFFFF)和纯黑(#000000),深色模式中使用深灰色表面 - 图片和图标使用 tint 着色而非提供两套资源
- 通过 Material Theme Builder 生成配套的浅色/深色配色方案
// 用 tint 替代两套图标
binding.icon.imageTintList = ColorStateList.valueOf(
MaterialColors.getColor(binding.icon, com.google.android.material.R.attr.colorOnSurface)
)
常见面试问题
Q1: 切换深色模式时 Activity 会重建吗?如何避免?
答案:
默认情况下,调用 setDefaultNightMode 会导致 Activity 重建(configChanges 触发)。如果想避免重建,可以在 AndroidManifest.xml 中声明:
<activity android:configChanges="uiMode" />
但这样需要手动在 onConfigurationChanged 中更新 UI。一般建议接受重建,通过 ViewModel 和 SavedState 保持数据。
Q2: WebView 如何适配深色模式?
答案:
AndroidX WebKit 提供了 WebSettingsCompat:
if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
WebSettingsCompat.setAlgorithmicDarkeningAllowed(webView.settings, true)
}
这让 WebView 自动对网页内容应用算法暗色处理。也可以通过 CSS 的 prefers-color-scheme 让网页自行适配。
Q3: 如何持久化用户的主题偏好?
答案:
使用 DataStore 或 SharedPreferences 存储用户选择,在 Application.onCreate 中恢复:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
val mode = PreferenceManager.getDefaultSharedPreferences(this)
.getInt("night_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
AppCompatDelegate.setDefaultNightMode(mode)
}
}