跳到主要内容

深色模式

问题

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)
}
}

相关链接