简单使用
设置夜间模式的类是AppCompatDelegate。
设置全局夜间模式
public static void setDefaultNightMode(@NightMode int mode) {
给页面单独设置夜间模式
@RequiresApi(17)
public abstract void setLocalNightMode(@NightMode int mode);
在Activity中调用,注意,要在setContentView之前:
getDelegate.setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES)
配置夜间模式资源
在资源文件夹中创建对应的夜间模式文件夹,后缀为-night。例如:values文件夹下的colors.xml,此时我们想给 <color name="bg">#FFFFFFFF</color> ,配置对应的夜间模式色值,先创建values-night文件夹,然后在文件夹下创建文件colors.xml,在xml文件中写入<color name="bg">#FF000000</color> 。此时夜间模式的资源就配置好了,在布局文件中使用即可,系统会根据代码的设置,读取对应的色值。
关于页面重建recreate
当我们给页面单独设置模式时,Activity会调用recreate方法,重新创建页面。重建之前,Activity的生命周期也会执行一遍。
private boolean updateForNightMode(@ApplyableNightMode final int mode,
final boolean allowRecreation) {
boolean handled = false;
final Configuration overrideConfig =
createOverrideConfigurationForDayNight(mContext, mode, null);
final boolean activityHandlingUiMode = isActivityManifestHandlingUiMode();
final int currentNightMode = mContext.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK;
final int newNightMode = overrideConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentNightMode != newNightMode
&& allowRecreation
&& !activityHandlingUiMode
&& mBaseContextAttached
&& (sCanReturnDifferentContext || mCreated)
&& mHost instanceof Activity
&& !((Activity) mHost).isChild()) {
ActivityCompat.recreate((Activity) mHost);
handled = true;
}
if (!handled && currentNightMode != newNightMode) {
updateResourcesConfigurationForNightMode(newNightMode, activityHandlingUiMode, null);
handled = true;
}
if (handled && mHost instanceof AppCompatActivity) {
((AppCompatActivity) mHost).onNightModeChanged(mode);
}
return handled;
}
因此,我们看到页面出现后马上消失了,然后再次出现,会闪一下。为了避免这种情况,我们可以在Manifest中为Activity配置configChanges
<activity
android:configChanges="uiMode"
/>
弹窗类Activity导致的前一个Activity颜色混乱
有时候我们会启动这样一个Acitivty,UI效果类似于一个dialog,并且需要在代码中单独设置了夜间模式。 一般我们会这样给Activity设置主题:
<activity
android:theme="@style/dialog_activity"
/>
<style name="dialog_activity" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="colorPrimaryDark">@android:color/transparent</item>
<!--设置动画,在这里使用让它继承系统的Animation.Dialog-->
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
<item name="android:windowBackground">@android:color/transparent</item>
<!--背景透明-->
<item name="android:backgroundDimEnabled">true</item>
<!-- 半透明 -->
<item name="android:windowIsTranslucent">true</item>
</style>
当关闭掉弹窗,回到前一个Activity时,Activity的部分UI会使用夜间模式,UI中存在RecyclerView时,比较容易复现。
目前不清楚出现这种问题的原因是什么,以下仅为猜测。因为window的背景是透明的,前一个Activity的部分UI对用户可见,那么当前一个Acitivty使用日间模式,dialog使用夜间模式时,为了保证用户的体验一致,即dialog和背景Activity都使用夜间模式,系统会暂时给背景Activity设置夜间模式。测试中,只要背景不透明,看不到背景Activity的UI,就不会发生颜色混乱。
解决办法
尝试了多种办法后,最终放弃了给弹窗类Activity单独设置夜间模式。取而代之,使用了主题色。
自定义一部分属性:
<attr name="dialog_st_text_lv1" format="color|reference"/>
<attr name="dialog_main_bg" format="color|reference"/>
<attr name="dialog_title_color" format="color|reference"/>
<attr name="dialog_title_bg" format="reference"/>
<attr name="dialog_edit_bg" format="reference"/>
<attr name="dialig_st_divider_lv1" format="reference|color"/>
然后添加日间模式主题,设置对应的资源:
<style name="dialog_activity.light">
<item name="dialog_main_bg">#ffffff</item>
<item name="dialog_st_text_lv1">#ff111111</item>
<item name="dialog_title_color">#333333</item>
<item name="dialog_title_bg">@drawable/bg_dialog_title_light</item>
<item name="dialog_edit_bg">@drawable/edittext_bg_light</item>
<item name="dialig_st_divider_lv1">#fff2f2f2</item>
</style>
添加夜间模式主题,设置对应的资源:
<style name="dialog_activity.dark">
<item name="dialog_main_bg">#191b27</item>
<item name="dialog_st_text_lv1">#ffe4e4e4</item>
<item name="dialog_title_color">#cfd1dc</item>
<item name="dialog_title_bg">@drawable/bg_dialog_title_dark</item>
<item name="dialog_edit_bg">@drawable/edittext_bg_dark</item>
<item name="dialig_st_divider_lv1">#ff07080b</item>
</style>
最后在layout中调用:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:theme="@style/dialog_activity.light">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/dialog_title_bg">
</FrameLayout>
</RelativeLayout>
不要忘记在代码中设置主题:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {Bundle bundle = getIntent().getExtras();
if (IS_NIGHT_MODE) {
setTheme(R.style.dialog_activity_light);
}else {
setTheme(R.style.dialog_activity_dark);
}
super.onCreate(savedInstanceState);
}
其他疑问
当一个弹窗类Activity,没有设置android:configChanges="uiMode" ,理所当然会进行recreate,但是仅限于第一次出现。如果再次弹窗就不会recreate,如果前一个Activity执行了onPause,再弹窗还是会出现一次recreate,不知道是不是有缓存
参考:
AppCompatDelegate API
|