「译」AppCompat v23.2 - 夜间模式最佳实践

如果看过了 Support Library 23.2.0 博客,你就会知道 AppCompat 现在有个新的主题:Theme.AppCompat.DayNight.
这个主题可以根据系统时间切换 Theme.AppCompat(暗色) 和 Theme.AppCompat.Light(亮色) 两种主题。这将会对应用的用户特别有用,特别是阅读类应用(这已经成为了阅读软件的标配)。需要注意的是,这个特性只支持 API v14 及以上的 Android 设备,在 API v14 以下的设备则会默认使用亮色的主题。

怎样使用 DayNight 主题?

DayNight 主题使用起来很简单,只需要把你的主题继承 DayNight 主题,然后应用。例如:

<!-- parent 为 Theme.AppCompat.DayNight -->
<style name="MyTheme" parent="Theme.AppCompat.DayNight">
    <!-- Blah blah -->
</style>

然后在程序中进行主题的初始化。你需要调用 AppCompatDelegate.setDefaultNightMode() ,它有四个参数:

  • MODE_NIGHT_NO. 使用亮色(light)主题
  • MODE_NIGHT_YES. 使用暗色(dark)主题
  • MODE_NIGHT_AUTO. 根据当前时间自动切换 亮色(light)/暗色(dark)主题
  • MODE_NIGHT_FOLLOW_SYSTEM(默认选项). 设置为跟随系统,通常为 MODE_NIGHT_NO

你可以在任何时候调用这个方法,因为这个方法是静态的。你设置的值不是一直存在的,所以你需要在每次程序开启进程时重新设置。我建议你在 application 类或者 Activity 中添加一个静态代码块来进行设置,比如:

static {
    AppCompatDelegate.setDefaultNightMode(
            AppCompatDelegate.MODE_NIGHT_...);
}
public class MyApplication extends Application {

设置程序日夜间模式 setLocalNightMode()

你可以通过调用 AppCompatDelegate 的 setLocalNightMode() 方法来重写每个组件的默认值。如果你知道有一些组件是使用 DayNight 方法的时候,这是很好用的。对与开发者来说,你不用等到太阳下山就可以测试夜间模式了呢。
请注意,这个方法并不会实时更新布局。你需要调用 recreate() 来通知页面更新。使用方式如下:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            // Set the local night mode to some value
            getDelegate().setLocalNightMode(
                    AppCompatDelegate.MODE_NIGHT_...);
            // 调用 recreate() 使设置生效
            recreate();
        }
    }
}

怎样获取应用当前的主题?

只需要检测资源配置:

int currentNightMode = getResources().getConfiguration().uiMode
        & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
    case Configuration.UI_MODE_NIGHT_NO:
        // Night mode is not active, we're in day time
    case Configuration.UI_MODE_NIGHT_YES:
        // Night mode is active, we're at night!
    case Configuration.UI_MODE_NIGHT_UNDEFINED:
        // We don't know what mode we're in, assume notnight
}

文字、图片适配(为什么我的应用看起来很奇怪?)

艾玛,我看不见字了(黑色文字黑色背景)。艾玛,我的图标怎么这么丑。
当这个功能更改了程序主题时,你需要确定你的 布局/样式/图片 都适配了亮色(light)和暗色(dark)主题。
这种资源适配的经验法则就是:尽可能的使用主题属性(theme attributes)。下面是一些需要重点了解的:

  • ?android:attr/textColorPrimary. 系统默认的文字颜色。在亮色(light)主题下,颜色接近黑色,在暗色(dark)主题下,颜色接近白色。Contains a disabled state.
  • ?attr/colorControlNormal. 系统默认的图标颜色

关于 WebView

需要重点注意 WebView. WebView 不能使用主题属性(theme attributes),并且我们很难控制网页内容的样式,所以很有可能你的 WebView 跟程序其他主题颜色不符。所以,请确认你的 WebView 页面的主题尽量跟应用当前主题相同。

坐标!坐标!!坐标!!!

想要精确的自动切换日夜间模式,需要获取系统的位置。这可能会让开发者感到菊花一紧,不过不用害怕,如果你的程序被授予了坐标权限(location permission),AppCompat 会试着获取 LocationManager 中上次保存的坐标,根据坐标计算日出日落时间。并不需要程序请求任何权限。
如果程序没有位置权限(或者 LocationManager 没有存储上次坐标的信息),那么,系统会默认设置为早上6点钟为日出,下午10点钟为日落,如果用户调整系统时间,当前的主题也会随之改变。

为什么不直接设置主题为 AUTO 呢?

让我们假设一下应用场景,帮你更清晰的思考这个问题:

  1. 更改主题,使它继承自 Theme.AppCompat.DayNight
  2. 添加用户切换主题的设置项。把用户设置的信息存储到本地(比如 SharedPreference)。根据用户选择的参数调用 setDefaultNightMode() 方法。

下次启动程序的时候,读取本地文件然后根据用户设置,调用 setDefaultNightMode() 方法。

重点来了:我们不希望用户在设定主题后,主题还会根据当前的时间突然改变。请记得,默认的主题是 MODE_NIGHT_FOLLOW_SYSTEM, 如果我们添加一个用户可以设置主题的功能,AppCompat 会默认使用

切换主题时使用自定义资源

使用自定义资源,只需要在 res 目录下创建对应的 values-night 文件夹并创建对应的 themes.xml 文件:

res/values/themes.xml

<style name="Theme.AppCompat.DayNight" 
       parent="Theme.AppCompat.Light" />

res/values-night/themes.xml

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

只要在对应的资源文件夹后添加 -night 后缀,比如:drawable-nightvalues-night, 等等…

Night night

我们不保证这种用法适用于全部应用,但是如果你在合适的时机使用,还是会很有帮助的。

查看原文:https://medium.com/@chrisbanes/appcompat-v23-2-daynight-d10f90c83e94#.35278z4j5

译者注:如果你想了解更多 AppCompat v23.2 的新特性,推荐你看一下秋百万的android-support-23.2-sample