return when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {

Configuration.UI_MODE_NIGHT_YES -> true

else -> false

}

}

Tips: 对于一些从网络接口服务获取的需要对深色模式区分的色值或者图片,可以使用上述的判断来获取对应的资源。

判断当前深色模式场景


通过AppCompatDelegate.getDefaultNightMode())可以获取五种深色模式场景:

  • MODE_NIGHT_AUTO_BATTERY 低电量模式自动开启深色模式

  • MODE_NIGHT_FOLLOW_SYSTEM 跟随系统开启和关闭深色模式(默认)

  • MODE_NIGHT_NO 强制使用notnight资源,表示非深色模式

  • MODE_NIGHT_YES 强制使用night资源

  • MODE_NIGHT_UNSPECIFIED 配合 setLocalNightMode(int)) 使用,表示由Activity通过AppCompactActivity.getDelegate()来单独设置页面的深色模式,不设置全局模式

模式设置


深色模式设置可以从三个层级设置,分别是系统层、Applcation层以及Activity层。底层的设置会覆盖上层的设置,例如系统设置了深色模式,但是Application设置了浅色模式,那么应用会显示浅色主题。

系统层是指系统设置中,根据不同产商的手机,可以在设置->显示中修改系统为深色模式。

Application层通过AppCompatDelegate.setDefaultNightMode()设置深色模式。

Activity层通过getDelegate().setLocalNightMode())设置深色模式。

当深色模式改变时,Activity会重建,如果不希望Activity重建,可以在AndroidManifest.xml中对对应的Activity设置android:configChanges=“uiMode”,不过设置之后页面的颜色改变需要Activity在中通过监听onConfigurationChanged来动态改变。

通过AppCompatDelegate.setDefaultNightMode(int))可以设置深色模式,源码如下:

public static void setDefaultNightMode(@NightMode int mode) {

if (DEBUG) {

Log.d(TAG, String.format(“setDefaultNightMode. New:%d, Current:%d”,

mode, sDefaultNightMode));

}

switch (mode) {

case MODE_NIGHT_NO:

case MODE_NIGHT_YES:

case MODE_NIGHT_FOLLOW_SYSTEM:

case MODE_NIGHT_AUTO_TIME:

case MODE_NIGHT_AUTO_BATTERY:

if (sDefaultNightMode != mode) {

sDefaultNightMode = mode;

applyDayNightToActiveDelegates();

}

break;

default:

Log.d(TAG, “setDefaultNightMode() called with an unknown mode”);

break;

}

}

从源码可以看出设置 MODE_NIGHT_UNSPECIFIED 模式是不会生效的。

Tips:注意,深色模式变化会导致Activity重建。

适配方案



自定义适配

1. 主题

将Application和Activity的主题修改为集成自Theme.AppCompat.DayNight或者Theme.MaterialComponents.DayNight,就可以对于大部分的控件得到较好的深色模式支持。我们看下DayNight主题的定义:

res/values/values.xml

<?xml version="1.0" encoding="utf-8"?>

res/values-night/themes.xml

Tips: 若需要动态修改主题要在调用inflate之前调用,否则不会生效。

2. 色值

主题切换颜色

除了定义不同模式使用不同的主题,我们还可以对主题设置自定义的色值。在设置主题色值之前,我们先了解一下Android主题的颜色系统。

  • colorPrimary:主要品牌颜色,一般用于ActionBar背景

  • colorPrimaryDark:默认用于顶部状态栏和底部导航栏

  • colorPrimaryVariant:主要品牌颜色的可选颜色

  • colorSecondary:第二品牌颜色

  • colorSecondaryVariant:第二品牌颜色的可选颜色

  • colorPrimarySurface:对应Light主题指向colorPrimary,Dark主题指向colorSurface

  • colorOn[Primary, Secondary, Surface …],在Primary等这些背景的上面内容的颜色,例如ActioBar上面的文字颜色

  • colorAccent:默认设置给colorControlActivated,一般是主要品牌颜色的明亮版本补充

  • colorControlNormal:图标和控制项的正常状态颜色

  • colorControlActivated:图标和控制项的选中颜色(例如Checked或者Switcher)

  • colorControlHighlight:点击高亮效果(ripple或者selector)

  • colorButtonNormal:按钮默认状态颜色

  • colorSurface:cards, sheets, menus等控件的背景颜色

  • colorBackground:页面的背景颜色

  • colorError:展示错误的颜色

  • textColorPrimary:主要文字颜色

  • textColorSecondary:可选文字颜色

Tips: 当某个属性同时可以通过 ?attr/xxx 或者?android:attr/xxx获取时,最好使用?attr/xxx,因为?android:attr/xxx是通过系统获取,而?attr/xxx是通过静态库类似于AppCompat 或者 Material Design Component引入的。使用非系统版本的属性可以提高平台通用性。

如果需要自定义主题颜色,我们可以对颜色分别定义notnight和night两份,放在values以及values-night资源文件夹中,并在自定义主题时,传入给对应的颜色属性。例如:

res/values/styles.xml

res/values/colors.xml

<?xml version="1.0" encoding="utf-8"?>

#4D71FF

#FFFFFF

#101214

#E0A62E

res/values-night/colors.xml

<?xml version="1.0" encoding="utf-8"?>

#FF584D

#0B0C0D

#F5F7FA

#626469

控件切换颜色

同样的,我们可以在布局的XML文件中直接使用定义好的颜色值,例如

<TextView

android:id="@+id/auto_color_text"

android:text=“自定义变色文字”

android:background="@drawable/bg_text"

android:textColor="@color/color_text_0" />

<?xml version="1.0" encoding="utf-8"?>

<shape xmlns:android=“http://schemas.android.com/apk/res/android”

android:shape=“rectangle”>

这样这个文字就会在深色模式中展示为黑底白字,在非深色模式中展示为白底黑字。

动态设置颜色

如果需要代码设置颜色,如果色值已经设置过notnight和night两份,那么直接设置颜色就可以得到深色模式变色效果。

auto_color_text.setTextColor(ContextCompat.getColor(this, R.color.color_text_0))

如果色值是从服务接口获取,那么可以使用上述深色模式的判断设置。

auto_color_text.setTextColor(if (isNightMode()) {

Color.parseColor(darkColorFromNetwork)

} else {

Color.parseColor(colorFromNetwork)

})

3. 图片&动画


普通图片&Gif图片

将图片分为明亮模式和深色模式两份,分别放置在drawable-night-xxx以及drawable-xxx文件夹中,并在view中直接使用即可,当深色模式切换时,会使用对应深色模式的资源。如下图所示:

Vector图片


在Vector资源定义时,通过指定画笔颜色来实现对深色模式的适配,例如:

<vector xmlns:android=“http://schemas.android.com/apk/res/android”

android:width=“24dp”

android:height=“24dp”

android:tint="@color/color_light"

android:viewportWidth=“24”

android:viewportHeight=“24”>

<path

android:fillColor="@android:color/white"

android:pathData=“M6.29,14.29L9,17v4c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1v-4l2.71,-2.71c0.19,-0.19 0.29,-0.44 0.29,-0.71L18,10c0,-0.55 -0.45,-1 -1,-1L7,9c-0.55,0 -1,0.45 -1,1v3.59c0,0.26 0.11,0.52 0.29,0.7zM12,2c0.55,0 1,0.45 1,1v1c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L11,3c0,-0.55 0.45,-1 1,-1zM4.21,5.17c0.39,-0.39 1.02,-0.39 1.42,0l0.71,0.71c0.39,0.39 0.39,1.02 0,1.41 -0.39,0.39 -1.02,0.39 -1.41,0l-0.72,-0.71c-0.39,-0.39 -0.39,-1.02 0,-1.41zM17.67,5.88l0.71,-0.71c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41l-0.71,0.71c-0.39,0.39 -1.02,0.39 -1.41,0 -0.39,-0.39 -0.39,-1.02 0,-1.41z” />

其中android:tint为叠加颜色,@color/color_light已经分别定义好了notnight和night的色值。

Lottie

对于Lottie动画,我们可以使用Lottie的Dynamic Properties特性来针对深色模式进行颜色变化。例如我们有以下两个动画,左边是由颜色填充的机器人,右边是由描边生成的正在播放动画,我们可以调用LottieAnimationView.resolveKeyPath()方法获取动画的路径。

lottie_android_animate.addLottieOnCompositionLoadedListener {

lottie_android_animate.resolveKeyPath(KeyPath("**")).forEach {

Log.d(TAG, it.keysToString())

}

setupValueCallbacks()

}

对于机器小人打印的KeyPath如下:

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [MasterController]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Head]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Head, Group 3]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Head, Group 3, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 2]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 2, Rectangle Path 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 2, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 1, Rectangle Path 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Blink, Rectangle 1, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Eyes]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Eyes, Group 3]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Eyes, Group 3, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [BeloOutlines]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [BeloOutlines, Group 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [BeloOutlines, Group 1, Stroke 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Shirt]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Shirt, Group 5]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Shirt, Group 5, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Body]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Body, Group 4]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [Body, Group 4, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftFoot]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftFoot, Group 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftFoot, Group 1, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightFoot]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightFoot, Group 2]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightFoot, Group 2, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave]

2020-09-10
15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm, Group 6]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm, Group 6, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm, Group 5]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [LeftArmWave, LeftArm, Group 5, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm, Group 6]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm, Group 6, Fill 1]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm, Group 5]

2020-09-10 15:30:55.762 29281-29281/com.shengj.androiddarkthemedemo D/DarkThemeDemo: [RightArm, Group 5, Fill 1]

我们抽取其中的某些形状来动态改变颜色,例如我们抽取左右手臂以及机器小人身上的T恤

private fun setupValueCallbacks() {

// 机器人右手臂

val rightArm = KeyPath(“RightArm”, “Group 6”, “Fill 1”)

// 机器人左手臂

val leftArm = KeyPath(“LeftArmWave”, “LeftArm”, “Group 6”, “Fill 1”)

// 机器人T恤

val shirt = KeyPath(“Shirt”, “Group 5”, “Fill 1”)

// 设置右手臂颜色

lottie_android_animate.addValueCallback(rightArm, LottieProperty.COLOR) {

ContextCompat.getColor(this, R.color.color_main_1)

}

// 设置左手臂颜色

lottie_android_animate.addValueCallback(shirt, LottieProperty.COLOR) {

ContextCompat.getColor(this, R.color.color_light)

}

// 设置T恤颜色

lottie_android_animate.addValueCallback(leftArm, LottieProperty.COLOR) {

ContextCompat.getColor(this, R.color.color_custom)

}

// 播放动画描边颜色

lottie_playing_animate.addValueCallback(KeyPath("**"), LottieProperty.STROKE_COLOR) {

ContextCompat.getColor(this, R.color.color_text_0)

}

}

由于color_main_1、color_light以及color_custom都已经定义过深色模式和明亮模式的色值,因此在深色模式切换时,Lottie动画的这个机器小人的左右手臂和T恤颜色会随着深色模式切换而变化。

同样的对于播放动画,我们也可以设置描边颜色,来达到深色模式切换的效果。

网络获取图片

对于网络获取的图片,可以让服务接口分别给出明亮模式和深色模式两套素材,然后根据上述的深色模式判断来进行切换

Glide.with(this)

.load(if(isNightMode() nightImageUrl else imageUrl))

.into(imgView)

Force Dark

看到这里可能会有人有疑问,对于大型的项目而言,里面已经hardcore了很多的颜色值,并且很多图片都没有设计成深色模式的,那做深色模式适配是不是一个不可能完成的任务呢?答案是否定的。对于大型项目而言,除了对所有的颜色和图片定义night资源的自定义适配方法外,我们还可以对使用Light风格主题的页面进行进行强制深色模式转换。

我们可以分别对主题和View设置强制深色模式。对于主题,在Light主题中设置android:forceDarkAllowed,例如:

对于View,设置View.setForceDarkAllowed(boolean))或者xml来设置是否支持Force Dark,默认值是true。

<View

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:forceDarkAllowed=“false”/>

这里需要注意的是,Force Dark的设置有以下几个规则:

  1. 要强制深色模式生效必须开启硬件加速(默认开启)

  2. 主题设置的Force Dark仅对Light的主题有效,对非Light的主题不管是设置android:forceDarkAllowed为true或者设置View.setForceDarkAllowed(true)都是无效的。

  3. 父节点设置了不支持Force Dark,那么子节点再设置支持Force Dark无效。例如主题设置了android:forceDarkAllowed为false,则View设置View.setForceDarkAllowed(true)无效。同样的,如果View本身设置了支持Force Dark,但是其父layout设置了不支持,那么该View不会执行Force Dark

  4. 子节点设置不支持Force Dark不受父节点设置支持Force Dark影响。例如View设置了支持Force Dark,但是其子Layout设置了不支持,那么子Layout也不会执行Force Dark。

Tips:一个比较容易记的规则就是不支持Force Dark优先,View 的 Force Dark设置一般会设置成 false,用于排除某些已经适配了深色模式的 View。

下面我们从源码出发来理解Force Dark的这些行为,以及看看系统是怎么实现Force Dark的。

Tips:善用 https://cs.android.com/ 源码搜索网站可以方便查看系统源码。

1. 主题

从主题设置的forceDarkAllowed入手查找,可以找到

frameworks/base/core/java/android/view/ViewRootImpl.java

private void updateForceDarkMode() {

if (mAttachInfo.mThreadedRenderer == null) return;

// 判断当前是否深色模式

boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;

// 如果当前是深色模式

if (useAutoDark) {

// 获取Force Dark的系统默认值

boolean forceDarkAllowedDefault =

SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);

TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);

// 判断主题是否浅色主题 并且 判断主题设置的forceDarkAllowed

useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)

&& a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);

a.recycle();

}

// 将是否强制使用深色模式赋值给Renderer层

if (mAttachInfo.mThreadedRenderer.setForceDark(useAutoDark)) {

// TODO: Don’t require regenerating all display lists to apply this setting

invalidateWorld(mView);

}

}

而这个方法正式在ViewRootImpl.enableHardwareAcceleration()方法中调用的,因此可以得到第一个结论:强制深色模式只在硬件加速下生效。由于userAutoDark变量会判断当前主题是否为浅色,因此可以得到第二个结论:强制深色模式只在浅色主题下生效。直到这一步的调用链如下:

mAttachInfo.mThreadedRenderer为ThreadRenderer,继承自HardwareRenderer,指定了接下来的渲染操作由RanderThread执行。继续跟踪setForceDark()方法:

frameworks/base/graphics/java/android/graphics/HardwareRenderer.java

public boolean setForceDark(boolean enable) {

// 如果强制深色模式变化

if (mForceDark != enable) {

mForceDark = enable;

// 调用native层设置强制深色模式逻辑

nSetForceDark(mNativeProxy, enable);

return true;

}

return false;

}

private static native void nSetForceDark(long nativeProxy, boolean enabled);

查找nSetForceDark()方法

frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp

static const JNINativeMethod gMethods[] = {

// …

// 在Android Runtime启动时,通过JNI动态注册

{ “nSetForceDark”, “(JZ)V”, (void*)android_view_ThreadedRenderer_setForceDark },

{ “preload”, “()V”, (void*)android_view_ThreadedRenderer_preload },

};

查找android_view_ThreadedRenderer_setForceDark()方法

frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp

static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,

jlong proxyPtr, jboolean enable) {

RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);

// 调用RenderProxy的setForceDark方法

proxy->setForceDark(enable);

}

frameworks/base/libs/hwui/renderthread/RenderProxy.cpp

void RenderProxy::setForceDark(bool enable) {

// 调用CanvasContext的setForceDark方法

mRenderThread.queue().post(this, enable { mContext->setForceDark(enable); });

}

frameworks/base/libs/hwui/renderthread/CanvasContext.h

// Force Dark的默认值是false

bool mUseForceDark = false;

// 设置mUseForceDark标志

void setForceDark(bool enable) { mUseForceDark = enable; }

bool useForceDark() {

return mUseForceDark;

}

接着查找调用userForceDark()方法的地方

frameworks/base/libs/hwui/TreeInfo.cpp

TreeInfo::TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext)
mode(mode)

, prepareTextures(mode == MODE_FULL)

, canvasContext(canvasContext)

// 设置disableForceDark变量

, disableForceDark(canvasContext.useForceDark() ? 0 : 1)

, screenSize(canvasContext.getNextFrameSize()) {}

} // namespace android::uirenderer

frameworks/base/libs/hwui/TreeInfo.h

class TreeInfo {

public:

// …

int disableForceDark;

// …

};

到了这里,可以看出,当设置了Force Dark之后,最终会设置到TreeInfo类中的disableForceDark变量,如果没有设置主题的Force Dark,那么根据false的默认值,disableForceDark变量会别设置成1,如果设置了使用强制深色模式,那么disableForceDark会变成0。

这个变量最终会用在RenderNode的RenderNode.handleForceDark()过程中,到达的流程如下图:

frameworks/base/libs/hwui/RenderNode.cpp

void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {

// …

// 同步正在处理的RenderNode Property变化

if (info.mode == TreeInfo::MODE_FULL) {

pushStagingPropertiesChanges(info);

}

// 如果当前View不允许被ForceDark,那么info.disableForceDark值+1

if (!mProperties.getAllowForceDark()) {

info.disableForceDark++;

}

// …

// 同步正在处理的Render Node的Display List,实现具体深色的逻辑

if (info.mode == TreeInfo::MODE_FULL) {

pushStagingDisplayListChanges(observer, info);

}

if (mDisplayList) {

info.out.hasFunctors |= mDisplayList->hasFunctor();

bool isDirty = mDisplayList->prepareListAndChildren(

observer, info, childFunctorsNeedLayer,

[](RenderNode* child, TreeObserver& observer, TreeInfo& info,

bool functorsNeedLayer) {

// 递归调用子节点的prepareTreeImpl。

// 递归调用之前,若父节点不允许强制深色模式,disableForceDark已经不为0,

// 子节点再设置允许强制深色模式不会使得disableForceDark的值减少,

// 因此有第三个规则:父节点设置了不允许深色模式,子节点再设置允许深色模式无效。

// 同样的,递归调用之前,若父节点允许深色模式,disableForceDark为0,

// 子节点再设置不允许强制深色模式,则disableForceDark值还是会++,不为0

// 因此有第四个规则:子节点设置不允许强制深色模式不受父节点设置允许强制深色模式影响。

child->prepareTreeImpl(observer, info, functorsNeedLayer);

});

if (isDirty) {

damageSelf(info);

}

}

pushLayerUpdate(info);

// 递归结束后将之前设置过+1的值做回退-1恢复操作,避免影响其他兄弟结点的深色模式值判断

if (!mProperties.getAllowForceDark()) {

info.disableForceDark–;

}

info.damageAccumulator->popTransform();

}

void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {

// …

// 同步DisplayList

syncDisplayList(observer, &info);

// …

}

void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {

// …

if (mDisplayList) {

WebViewSyncData syncData {

// 设置WebViewSyncData的applyForceDark

.applyForceDark = info && !info->disableForceDark

};

mDisplayList->syncContents(syncData);

// 强制执行深色模式执行

handleForceDark(info);

}

}

void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {

if (CC_LIKELY(!info || info->disableForceDark)) {

// 如果disableForceDark不为0,关闭强制深色模式,则直接返回

return;

}

auto usage = usageHint();

const auto& children = mDisplayList->mChildNodes;

// 如果有文字表示是前景策略

if (mDisplayList->hasText()) {

usage = UsageHint::Foreground;

}

if (usage == UsageHint::Unknown) {

// 如果子节点大于1或者第一个子节点不是背景,那么设置为背景策略

if (children.size() > 1) {

usage = UsageHint::Background;

} else if (children.size() == 1 &&

children.front().getRenderNode()->usageHint() !=

UsageHint::Background) {

usage = UsageHint::Background;

}

}

if (children.size() > 1) {

// Crude overlap check

SkRect drawn = SkRect::MakeEmpty();

for (auto iter = children.rbegin(); iter != children.rend(); ++iter) {

const auto& child = iter->getRenderNode();

// We use stagingProperties here because we haven’t yet sync’d the children

SkRect bounds = SkRect::MakeXYWH(child->stagingProperties().getX(), child->stagingProperties().getY(),

child->stagingProperties().getWidth(), child->stagingProperties().getHeight());

if (bounds.contains(drawn)) {

// This contains everything drawn after it, so make it a background

child->setUsageHint(UsageHint::Background);

}

drawn.join(bounds);

}

}

// 根据前景还是背景策略对颜色进行提亮或者加深

mDisplayList->mDisplayList.applyColorTransform(

usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);

}

Dark(android::uirenderer::TreeInfo *info) {

if (CC_LIKELY(!info || info->disableForceDark)) {

// 如果disableForceDark不为0,关闭强制深色模式,则直接返回

return;

}

auto usage = usageHint();

const auto& children = mDisplayList->mChildNodes;

// 如果有文字表示是前景策略

if (mDisplayList->hasText()) {

usage = UsageHint::Foreground;

}

if (usage == UsageHint::Unknown) {

// 如果子节点大于1或者第一个子节点不是背景,那么设置为背景策略

if (children.size() > 1) {

usage = UsageHint::Background;

} else if (children.size() == 1 &&

children.front().getRenderNode()->usageHint() !=

UsageHint::Background) {

usage = UsageHint::Background;

}

}

if (children.size() > 1) {

// Crude overlap check

SkRect drawn = SkRect::MakeEmpty();

for (auto iter = children.rbegin(); iter != children.rend(); ++iter) {

const auto& child = iter->getRenderNode();

// We use stagingProperties here because we haven’t yet sync’d the children

SkRect bounds = SkRect::MakeXYWH(child->stagingProperties().getX(), child->stagingProperties().getY(),

child->stagingProperties().getWidth(), child->stagingProperties().getHeight());

if (bounds.contains(drawn)) {

// This contains everything drawn after it, so make it a background

child->setUsageHint(UsageHint::Background);

}

drawn.join(bounds);

}

}

// 根据前景还是背景策略对颜色进行提亮或者加深

mDisplayList->mDisplayList.applyColorTransform(

usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);

}

Android深色模式适配原理分析,android应用开发相关推荐

  1. android自动切换暗色,超实用!Android 深色模式适配(可定时开启的APP内主题切换管理工具)...

    前言 前面分享了一篇"黑白化主题"的文,主要适用场景是不久就要到来的"清明"等时节或者是其他的国家公祭日什么的(一名成熟的程序员,要学会自己提产品需求). 今天 ...

  2. android doze模式源码分析,Android Doze模式启用和恢复详解

    从Android 6.0(API level 23)开始,Android提出了两个延长电池使用时间的省电特性给用户.用户管理可以在没有充电的情况下管理app的行为.当用户一段时间没有使用手机的时候,D ...

  3. Android 暗黑模式适配

    Android 暗黑模式适配 不爱废话直接上菜. 第一种适配方式: 1.1 直接修改APP主题,values/styles.xml 如下: <style name="AppTheme& ...

  4. Android手机一键Root原理分析(作者:非虫,文章来自:《黑客防线》2012年7月)

    之前几天都在做Android漏洞分析的项目,万幸发现了这篇文章.废话不多说,上文章! <Android手机一键Root原理分析> (作者:非虫,文章来自:<黑客防线>2012年 ...

  5. Android系统的JNI原理分析(二)- 数据类型转换和方法签名

    声明 前阶段在项目中使用了Android的JNI技术,在此文中做些技术知识总结. 本文参考了一些书籍的若干章节,比如<Android进阶解密-第9章-JNI原理>.<深入理解Andr ...

  6. 事件争夺战 Android事件处理机制与原理分析

    事件争夺战 Android事件处理机制与原理分析 文章目录 事件争夺战 Android事件处理机制与原理分析 View的继承关系 View的事件处理源码 总结: ViewGroup的事件分发源码 总结 ...

  7. Android锁屏机制原理分析

    转载自:http://www.2cto.com/kf/201401/273898.html 春节前最后几天了,工作上几乎没有什么要做.大致整理下之前工作中写的文档,PPT,手册. 由于去年一年完全转到 ...

  8. cocos2d-x 屏幕适配原理分析

    cocos2d-x作为著名的cocos2d游戏开发框架的C++实现者,最近一年发展迅猛.越来越多的app使用它实现快速多平台部署,从最初的ios,android,win32等到新近的html5,实现移 ...

  9. Android 6.0 JNI原理分析 和 Linux系统调用(syscall)原理

    JNI原理 引言:分析Android源码6.0的过程,一定离不开Java与C/C++代码直接的来回跳转,那么就很有必要掌握JNI,这是链接Java层和Native层的桥梁,本文涉及相关源码: fram ...

最新文章

  1. System.Runtime.InteropServices.Marshal.GetTypeFromCLSID(System.Guid) 问题解决方法
  2. Windows XP硬盘安装Ubuntu 12.04双系统
  3. C++实现插入排序算法
  4. PHP快速入门教程:WHILE循环示例
  5. 变频电源的日常检查目录
  6. NLTK找出最频繁的名词标记的程序(代码详细解释)
  7. C++动态链接库的制作
  8. MySQL—ERROR 2002 (HY000): '/var/lib/mysql/mysql.sock'解决记录
  9. linux 防火墙 qt udp,使用Qt实现简单的udp/ip通信
  10. java加密不可逆,32位不可逆加密算法Java实现
  11. 统计学名词解释 —— 3. 「简单随机样本」、「联合分布」与「联合密度」
  12. DataSource
  13. prisma2.0和nexus搭建graphql后端(2)—nexus
  14. DOJP1520星门跳跃题解
  15. 手机和电脑如何连接无线投影仪
  16. UBUNTU 22.04 使用 SUNSHINE 和 MOONLIGHT 进行串流
  17. REST Assured 2 - 用IDEA创建一个基本的REST Assured Maven项目
  18. php携程 线程,php 如何获取这个携程页面的数据
  19. 看看gps信息发布平台
  20. ARM和NEON指令

热门文章

  1. dirac 是什么?
  2. java假期顺延求一个日期n天后的日期(排除节假日及周六周日)
  3. flash 与分解:分解百度MP3图片墙
  4. Unity Shader - Billboard 广告板/广告牌 - BB树,BB投影
  5. 如何利用长尾关键词挖掘推广旅游业
  6. VBIDE.VBE的应用
  7. windows+onlyoffice安装
  8. Office系列版本安装包下载
  9. 电梯默纳克系统服务器,电梯人必看:默纳克系统运行中报E51原因分析和处理方法...
  10. 使用hive进行大数据项目分析