实现深色主题

尝试使用 Compose 方式
Jetpack Compose 是推荐用于 Android 的界面工具包。了解如何在 Compose 中使用主题。

图 1. 深色主题。

深色主题适用于 Android 10(API 级别 29)及更高版本。它具有以下优势:

  • 可以大幅减少耗电量(具体取决于设备的屏幕技术)。
  • 为弱视以及对强光敏感的用户提高可视性。
  • 让用户在光线较暗的环境中更轻松地使用设备。

深色主题会应用于 Android 系统界面和设备上运行的应用。

在 Android 10 及更高版本中,您可以通过以下三种方式启用深色主题:

  • 前往设置 > 显示 > 主题,使用系统设置启用深色主题。
  • 启用后,您可以使用“快捷设置”功能块从通知栏中切换主题。
  • 在 Pixel 设备上,启用省电模式的同时也会启用深色主题。其他设备可能不支持此行为。

有关使用 WebView 组件对基于网络的内容应用深色主题的说明,请参阅使用 WebView 调暗网页内容的颜色

在应用中支持深色主题

如要支持深色主题,请将应用的主题(通常可在 res/values/styles.xml 中找到)设置为继承 DayNight 主题:

<style name="AppTheme" parent="Theme.AppCompat.DayNight">

您还可以使用 Material Components 深色主题

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">

这会将应用的主要主题与系统控制的夜间模式标记关联起来,并在启用夜间模式时将应用的默认主题设置为深色主题。

主题背景和样式

避免使用适合在浅色主题下使用的硬编码颜色或图标。请改用主题属性或适合在夜间使用的资源。

有两个主题属性对于深色主题最为重要:

  • ?android:attr/textColorPrimary:一种通用型文字颜色。它在浅色主题下接近于黑色,在深色主题下接近于白色。它包含一个停用状态。
  • ?attr/colorControlNormal:一种通用型图标颜色。它包含一个停用状态。

我们建议使用 Material Design 组件,因为通过它的颜色主题系统(例如主题属性 ?attr/colorSurface?attr/colorOnSurface)可以轻松获取合适的颜色。您可以在主题中自定义这些属性。

更改应用内主题

您可以允许用户在应用运行时更改应用的主题。建议选择以下选项:

  • 浅色
  • 深色
  • 系统默认设置(推荐的默认选项)

这些选项直接映射到 AppCompat.DayNight 模式:

如需切换主题,请执行以下操作:

Force Dark

Android 10 提供了 Force Dark 功能,此功能可让开发者快速实现深色主题,而无需明确设置 DayNight 主题。

Force Dark 会分析您采用浅色主题的应用的各个视图,并在相应视图在屏幕上显示之前,自动应用深色主题。您可以混合使用 Force Dark 和原生实现,以缩短实现深色主题所需的时间。

应用必须通过在 activity 的主题中设置 android:forceDarkAllowed="true" 来选择启用 Force Dark。此属性会在所有系统和 AndroidX 提供的浅色主题(例如 Theme.Material.Light)上设置。使用 Force Dark 时,请全面测试应用,并根据需要排除视图。

如果应用使用的是深色主题(例如 Theme.Material),则系统不会应用 Force Dark。同样,如果应用的主题继承自 DayNight 主题,系统也不会应用 Force Dark,因为会自动切换主题。

在视图上停用 Force Dark

您可以通过 android:forceDarkAllowed 布局属性或 setForceDarkAllowed() 在具体视图上控制 Force Dark。

Web 内容

如需了解如何在基于网络的内容中使用深色主题,请参阅使用 WebView 调暗网页内容的颜色。如需查看将深色主题应用于 WebView 的示例,请参阅 GitHub 上的 WebView 演示

最佳做法

下面几个部分介绍了实现深色主题的最佳做法。

通知和 widget

对于在设备上显示但您并不直接控制的界面,务必要确保您使用的所有视图都反映托管应用的主题。通知和启动器 widget 是两个示例。

通知

使用系统提供的通知模板,例如 MessagingStyle。这意味着,系统将负责应用正确的视图样式。

微件和自定义通知视图

对于启动器 widget,或者如果您的应用使用自定义通知内容视图,请针对浅色和深色主题测试内容。

需要注意的常见陷阱包括:

  • 假设背景颜色始终为浅色。
  • 对文字颜色进行硬编码。
  • 设置硬编码背景颜色,同时使用默认文字颜色。
  • 使用采用静态颜色的可绘制图标。

在所有这些情况下,应使用适当的主题属性,而不是硬编码颜色。

启动屏幕

如果您的应用有自定义启动画面,您可能需要对其进行修改,以便它可以反映所选的主题。

移除所有硬编码颜色,例如以编程方式设置为白色的背景颜色。改用 ?android:attr/colorBackground 主题属性。

配置变更

当应用的主题发生更改(无论是通过系统设置还是 AppCompat)时,会触发 uiMode 配置变更。这意味着系统会自动重新创建 activity。

在某些情况下,您可能希望应用来处理配置变更。例如,您可能希望延迟配置变更时间,因为正在播放视频。

应用可以通过声明每个 Activity 都可以处理 uiMode 配置变更,自行处理深色主题的实现:

<activity
    android:name=".MyActivity"
    android:configChanges="uiMode" />

当某个 Activity 声明它会处理配置变更时,系统会在出现主题变更时调用其 onConfigurationChanged() 方法。

如要检查当前采用的是哪种主题背景,应用可以运行如下代码:

Kotlin

val currentNightMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (currentNightMode) {
    Configuration.UI_MODE_NIGHT_NO -> {} // Night mode is not active, we're using the light theme.
    Configuration.UI_MODE_NIGHT_YES -> {} // Night mode is active, we're using dark theme.
}

Java

int currentNightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
    case Configuration.UI_MODE_NIGHT_NO:
        // Night mode is not active, we're using the light theme
        break;
    case Configuration.UI_MODE_NIGHT_YES:
        // Night mode is active, we're using dark theme
        break;
}