一、简介
我们一直以来使用的操作系统都是以浅色主题为主的,这种主题模式在白天或者是光线充足的 情况下使用起来没有任何问题,可是在夜晚灯光关闭的情况下使用就会显得非常刺眼。 于是,许多应用程序为了能够让用户在光线昏暗的环境下更加舒适地使用,会在应用内部提供 一个一键切换夜间模式的按钮。当用户开启了夜间模式,就会将应用程序的整体色调都调整成 更加适合于夜间浏览的颜色。 不过,这种由应用程序自发实现夜间模式的方式很难做到全局统一,即有些应用可能支持夜间 模式,有些应用却不支持。而且重复操作的问题也很让人头疼,比如说我在一个应用中开启了 夜间模式,在另外一个应用中还需要再开启一次,关闭夜间模式也需要进行同样重复的操作。 因此,很多开发者一直呼吁,希望Android能够在系统层面支持夜间模式功能。
终于在Android 10.0系统中,Google引入了深色主题这一特性,从而让夜间模式正式成为了官方支持的功能。 或许你会有些疑惑,这种看上去并没有太多技术难度的功能,为什么Android直到10.0系统中 才进行支持呢?这是因为仅仅操作系统自身支持深色主题是没有用的,还得让所有的应用程序 都能够支持才行,而这从来都不是一件容易的事情。 为此,我希望你以后开发的应用程序都能够按照Android系统的要求对深色主题进行很好地支 持,不然当用户开启了深色主题之后,只有你的应用还使用的是浅色主题的话,就会显得格格 不入。
除了让眼部在夜间使用时更加舒适之外,深色主题还可以减少电量消耗,从而延长手机续航, 是一项非常有用的功能。
二、Force Dark
它是一种能让应用程序快速适配深色主题,并且 几乎不用编写额外代码的方式。Force Dark的工作原理是系统会分析浅色主题应用下的每一层 View,并且在这些View绘制到屏幕之前,自动将它们的颜色转换成更加适合深色主题的颜色。 注意,只有原本使用浅色主题的应用才能使用这种方式,如果你的应用原本使用的就是深色主题,Force Dark将不会起作用。
这里我们尝试对MaterialTest项目使用Force Dark转换来进行举例。启用Force Dark功能需要 借助android:forceDarkAllowed属性,不过这个属性是从API 29,也就是Android 10.0 系统开始才有的,之前的系统无法指定这个属性。因此,我们得进行一些系统差异型编程才 行。
1、右击res目录→New→Directory,创建一个values-v29目录,然后右击values-v29目录 →New→Values resource file,创建一个styles.xml文件。接着对这个文件进行编写,代码如 下所示:
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:forceDarkAllowed">true</item>
</style>
</resources>
除了android:forceDarkAllowed属性之外,其他的内容都是从之前的styles.xml文件中复 制过来的。这里给AppTheme主题增加了android:forceDarkAllowed属性并设置为 true,说明现在我们是允许系统使用Force Dark将应用强制转换成深色主题的。另外, values-v29目录是只有Android 10.0及以上的系统才会去读取的,因此这是一种系统差异型编 程的实现方式。
Force Dark就是这样一种简单粗暴的转换方式,并且它的转换效果通常是不尽如人意的。因 此,这里我并不推荐你使用这种自动化的方式来实现深色主题,而是应该使用更加传统的实现 方式——
三、手动实现
要想实现最佳的深色主题效果,不要指望有什么神奇魔法能够一键完成,而是应该针对 每一个界面都进行浅色和深色两种主题的界面设计。这听上去好像有点复杂,不过我们仍然有 一些好用的技巧能让这个过程变得简单。
AppCompat库内置的主题恰好主要分为浅色主题和深色主题两 类,比如MaterialTest项目中目前使用的Theme.AppCompat.Light.NoActionBar就是浅色 主题,而Theme.AppCompat.NoActionBar就是深色主题。选用不同的主题,在控件的默认 颜色等方面会有完全不同的效果。
而现在,我们多了一个DayNight主题的选项。使用了这个主题后,当用户在系统设置中开启深 色主题时,应用程序会自动使用深色主题,反之则会使用浅色主题。
1、首先删除values-v29目录及其目录下的内容,然后修改 values/styles.xml中的代码
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
...
</resources>
可以看到,这里我们将AppTheme的parent主题指定成了 Theme.AppCompat.DayNight.NoActionBar,这是一种DayNight主题。因此,在普通情况 下MaterialTest项目仍然会使用浅色主题,和之前并没有什么区别,但是一旦用户在系统设置 中开启了深色主题,MaterialTest项目就会自动使用相应的深色主题。
2、DayNight主题的效果
虽然现在界面中的主要内容都已经自动切换成了深色主题,但是你会发现标题栏和悬浮 按钮仍然保持着和浅色主题时一样的颜色。这是因为标题栏以及悬浮按钮使用的是我们定义在 colors.xml中的几种颜色值,代码如下所示:
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
这种指定颜色值引用的方式相当于对控件的颜色进行了硬编码,DayNight主题是不能对这些颜 色进行动态转换的。
好在解决方案也并不复杂,我们只需要进行一些主题差异型编程就可以了。右击res目录 →New→Directory,创建一个values-night目录,然后右击values-night目录 →New→Values resource file,创建一个colors.xml文件。接着在这个文件中指定深色主题下 的颜色值
虽说使用主题差异型的编程方式几乎可以帮你解决所有的适配问题,但是在DayNight主题下, 我们最好还是尽量减少通过硬编码的方式来指定控件的颜色,而是应该更多地使用能够根据当 前主题自动切换颜色的主题属性。比如说黑色的文字通常应该衬托在白色的背景下,反之白色 的文字通常应该衬托在黑色的背景下,那么此时我们就可以使用主题属性来指定背景以及文字 的颜色,示例写法如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Hello world"
android:textSize="40sp"
android:textColor="?android:attr/textColorPrimary" />
</FrameLayout>
这些主题属性会自动根据系统当前的主题模式选择最合适的颜色值呈现给用户
另外,或许你还会有一些特殊的需求,比如要在浅色主题和深色主题下分别执行不同的代码逻 辑。对此Android也是支持的,你可以使用如下代码在任何时候判断当前系统是否是深色主题:
fun isDarkTheme(context: Context): Boolean {
val flag = context.resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK
return flag == Configuration.UI_MODE_NIGHT_YES
}
调用isDarkTheme ()方法,判断当前系统是浅色主题还是深色主题,然后根据返回值执行不 同的代码逻辑即可。
另外,由于Kotlin取消了按位运算符的写法,改成了使用英文关键字,因此上述代码中的and关 键字其实就对应了Java中的&运算符,而Kotlin中的or关键字对应了Java中的|运算符,xor关 键字对应了Java中的^运算符,非常好理解。
最后贴上自己的