recreate(); // 这个是刷新,不然不起作用
有四个模式可以选
MODE_NIGHT_NO // 日间模式,使用light theme
MODE_NIGHT_YES // 夜间模式,使用dark theme
MODE_NIGHT_AUTO // 根据系统时间自动切换
MODE_NIGHT_FOLLOW_SYSTEM // 跟随系统设置,这个是默认的
3.2、通过 forceDarkAllowed 启用
如果嫌上面的方案麻烦,麻烦也有更简单的方案,那就是forceDarkAllowed
强制转换,但是效果可能就没有自己适配那么完美了。
首先,可以通过在主题中添加 android:forceDarkAllowed="true"
标记,这样系统在夜间模式时,会强制改变应用颜色,自动进行适配。不过如果你的应用本身使用的就是 DayNight
或 Dark Theme
,forceDarkAllowed
是不会生效的。
另外,如果你不希望某个 view 被强制夜间模式处理,则可以给 view 添加 android:forceDarkAllowed="false"
或者 view.setForceDarkAllowed(false)
,设置之后,即使打开了夜间模式且主题添加了 forceDarkAllowed
,该 view 也不会变深色。比较重要的一点是,这个接口只能关闭夜间模式,不能开启夜间模式,也就是说,如果主题中没有显示声明 forceDarkAllowed
,view.setForceDarkAllowed(true)
是没办法让 view 单独变深色的。如果 view 关闭了夜间模式,那么它的子 view 也会强制关闭夜间模式
总结如下:
-
主题若添加
forceDarkAllowed=false
,无论 view 是否开启forceDarkAllowed
都不会打开夜间模式 -
主题若添加
forceDarkAllowed=true
,view 可以通过forceDarkAllowed
关闭夜间模式,一旦关闭,子 view 的夜间模式也会被关闭 -
如果父 view 或主题设置了
forceDarkAllowed=false
,子 view 无法通过forceDarkAllowed=true
单独打开夜间模式为 -
若使用的是
DayNight
或Dark Theme
主题,则所有forceDarkAllowed
都不生效
四、实现原理
上面我们说的 android:forceDarkAllowed
其实是分为两个用处,它们分别的定义如下:
Activity Theme级别:
//frameworks/base/core/res/res/values/attrs.xml
View级别:
//frameworks/base/core/res/res/values/attrs.xml
4.1、Theme级别
熟悉View树的构造原理的同学应该都知道,ViewRootImpl
是View中的最高层级,属于所有View的根,所以该级别,我们需要在ViewRootImpl
中查找原因,寻寻觅觅,最终在updateForceDarkMode
函数中找到关于forceDarkAllowed
属性的踪影
//frameworks/base/core/java/android/view/ViewRootImpl.java
private void updateForceDarkMode() {
//渲染线程为空,直接返回
if (mAttachInfo.mThreadedRenderer == null) return;
//判断当前uimode是否开启深色模式
boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
if (useAutoDark) {
//读取开发者选项中强制smart dark的值
boolean forceDarkAllowedDefault =
SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
//读取Theme是浅色主题或深色主题,并且配置了forceDarkAllowed=true,
useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
&& a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
a.recycle();
}
//是否强制使用深色模式
if (mAttachInfo.mThreadedRenderer.setForceDark(useAutoDark)) {
// TODO: Don’t require regenerating all display lists to apply this setting
invalidateWorld(mView);
}
}
updateForceDarkMode
调用的时机分别是在 ViewRootImpl#setView
和 ViewRootImpl#updateConfiguration
,也就是DecorView
初始化和uimode
切换的时候调用,确保在设置中切换深色模式时,能通知ViewRootImpl
进行界面刷新。
我们继续跟踪 下HardwareRenderer#setForceDark
函数
//frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
public boolean setForceDark(boolean enable) {
//当forceDark值发生变化才会进入下面逻辑,否则返回false,无需刷新界面
if (mForceDark != enable) {
mForceDark = enable;
nSetForceDark(mNativeProxy, enable);
return true;
}
return false