在 2019 年的 Google I/O上,谷歌新发布的android 10终于从系统层级支持暗黑模式,那么为什么我们需要暗黑模式?Android开发者应该如何让自己的app适配暗黑模式?接下来的文章将一一为你解答。
在Android 官方文档中,列举了暗黑模式的三个好处:
在OLED显示屏上,当一个像素是纯黑色(十六进制为#000000)的时候,该像素将会被关闭并且不消耗能量,这时如果显示屏显示的是大面积的黑色像素,将会大大降低显示屏消耗的电量。
Android 10 提供 Force Dark 功能。此功能可让开发者快速实现深色主题背景,只需要在 style.xml 中的应用主题中添加这一行代码android:forceDarkAllowed=“true” ,就可以完成自动适配。
<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>
效果对比如下图所示。从结果来看,整体的界面风格好像确实变成了暗黑模式,但是菜单栏并未适配,所以这里我并不推荐你使用这种自动化的方式来实现深色主题,而是应该使用更加复杂一点的实现方式——自定义适配。
|
|
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
--colors.xml
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="colorTextView">#000</color>
</resources>
--colors-night.xml
<resources>
<color name="colorPrimary">#303030</color>
<color name="colorPrimaryDark">#232323</color>
<color name="colorAccent">#008577</color>
<color name="colorTextView">#FFFFFF</color>
</resources>
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorTextView"
android:padding="10dp"
android:textSize="20sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
效果对比如下图。效果明显比自动适配好上不少,算是初步实现了对暗黑模式的适配。
|
|
目前为止我们已经知道了如何适配暗黑模式,在完成适配之后,我们还需要为用户提供在运行时,切换主题的选项,切换的代码也很简单,在菜单监听中通过getDelegate().setLocalNightMode()来设置当前的模式。
@Override
public boolean onOptionsItemSelected(MenuItem mi){
if(mi.isCheckable()){
mi.setChecked(true);
}
switch (mi.getItemId()){
case R.id.mode_light:
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO);
break;
case R.id.mode_dark:
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
break;
case R.id.mode_system:
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
break;
}
return true;
}
setLocalNightMode()方法接收一个mode参数,用于控制当前应用程序的夜间模式。mode参数主要有以下值可供选择:
需要注意的是,当调用setLocalNightMode()方法并成功切换主题时,应用程序中所有处于started状态的Activity都会被重新创建,那如果不想Activity重新创建Activity怎么切换主题呢?
这时候我们可以在AndroidManifest中将configChanges设置为uiMode,使当前的Activity避免被重新创建。
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:configChanges="uiMode"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
现在当应用程序的主题发生变化时,MainActivity并不会重新创建,而是会触发onConfigurationChanged()方法的回调,你可以在回调当中手动做一些逻辑处理。
override fun onConfigurationChanged(newConfig: Configuration) {
val currentNightMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (currentNightMode) {
Configuration.UI_MODE_NIGHT_NO -> {} // 夜间模式未启用,使用浅色主题
Configuration.UI_MODE_NIGHT_YES -> {} // 夜间模式启用,使用深色主题
}
}
还有一点需要注意的是,切换逻辑仅在运行时生效,当我们重新启动 App 的时候,会与当前系统设置的模式保持一致。