1. 簡介
隨著 Android Q 發(fā)布卖擅,「黑暗模式」或者說是「夜間模式」終于在此版本中得到了支持,官方介紹見:https://developer.android.com/guide/topics/ui/look-and-feel/darktheme坟冲,再看看效果圖:
其實(shí)這個(gè)功能魅族在兩年前就已支持磨镶,不得不說 Android 有點(diǎn)落后了,今天我們就來看看原生是怎么實(shí)現(xiàn)全局夜間模的吧
2. 打開與關(guān)閉
從文檔上我們可以可知健提,打開夜間模式有三個(gè)方法:
- 設(shè)置 -> 顯示 -> 深色主題背景
- 下拉通知欄中開啟
- Pixel 手機(jī)開啟省點(diǎn)模式時(shí)會(huì)自動(dòng)激活夜間模式
3. 如何適配
打開后琳猫,我們會(huì)發(fā)現(xiàn),除原生幾個(gè)應(yīng)用生效外私痹,其他應(yīng)用依然沒有變成深色主題脐嫂,那么應(yīng)用該如何適配呢?官方提供了下面兩種方法:
3.1. 讓應(yīng)用主題繼承 DayNight
主題
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
或者繼承自
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
繼承后紊遵,如果當(dāng)前開啟了夜間模式账千,系統(tǒng)會(huì)自動(dòng)從 night-qualified 中加載資源,所以應(yīng)用的顏色暗膜、圖標(biāo)等資源應(yīng)盡量避免硬編碼匀奏,而是推薦使用新增 attributes 指向不同的資源,如
?android:attr/textColorPrimary
?attr/colorControlNormal
另外学搜,如果應(yīng)用希望主動(dòng)切換夜間/日間模式娃善,可以通過 AppCompatDelegate.setDefaultNightMode()
接口主動(dòng)切換
3.2. 通過 forceDarkAllowed 啟用
如果應(yīng)用不想自己去適配各種顏色,圖標(biāo)等瑞佩,可以通過在主題中添加 android:forceDarkAllowed="true"
標(biāo)記聚磺,這樣系統(tǒng)在夜間模式時(shí),會(huì)強(qiáng)制改變應(yīng)用顏色炬丸,自動(dòng)進(jìn)行適配(這個(gè)功能也是本文主要探討的)瘫寝。不過如果你的應(yīng)用本身使用的就是 DayNight
或 Dark Theme
,forceDarkAllowed 是不會(huì)生效的稠炬。
另外焕阿,如果你不希望某個(gè) view 被強(qiáng)制夜間模式處理,則可以給 view 添加 android:forceDarkAllowed="false"
或者 view.setForceDarkAllowed(false)首启,設(shè)置之后捣鲸,即使打開了夜間模式且主題添加了 forceDarkAllowed,該 view 也不會(huì)變深色闽坡。比較重要的一點(diǎn)是栽惶,這個(gè)接口只能關(guān)閉夜間模式愁溜,不能開啟夜間模式,也就是說外厂,如果主題中沒有顯示聲明 forceDarkAllowed冕象,view.setForceDarkAllowed(true)
是沒辦法讓 view 單獨(dú)變深色的。如果 view 關(guān)閉了夜間模式汁蝶,那么它的子 view 也會(huì)強(qiáng)制關(guān)閉夜間模式
總結(jié)如下:
- 主題若添加 forceDarkAllowed=false渐扮,無論 view 是否開啟 forceDarkAllowed 都不會(huì)打開夜間模式
- 主題若添加 forceDarkAllowed=true,view 可以通過 forceDarkAllowed 關(guān)閉夜間模式掖棉,一旦關(guān)閉墓律,子 view 的夜間模式也會(huì)被關(guān)閉
- 如果父 view 或主題設(shè)置了 forceDarkAllowed=false,子 view 無法通過 forceDarkAllowed=true 單獨(dú)打開夜間模式為
- 若使用的是
DayNight
或Dark Theme
主題幔亥,則所有 forceDarkAllowed 都不生效
4. 實(shí)現(xiàn)原理
通過繼承主題適配夜間模式的原理本質(zhì)是根據(jù) ui mode 加載 night-qualified 下是資源耻讽,這個(gè)并非 Android Q 新增的東西,我們這里不再描述∨撩蓿現(xiàn)在主要來看看 forceDarkAllowed 是如何讓系統(tǒng)變深色的针肥。
既然一切的源頭都是 android:forceDarkAllowed
這個(gè)屬性,那我們就從它入手吧香伴,首先我們要知道慰枕,上面我們說的 android:forceDarkAllowed
其實(shí)是分為兩個(gè)用處,它們分別的定義如下:
frameworks/base/core/res/res/values/attrs.xml
<declare-styleable name="View">
<!-- <p>Whether or not the force dark feature is allowed to be applied to this View.
<p>Setting this to false will disable the auto-dark feature on this View draws
including any descendants.
<p>Setting this to true will allow this view to be automatically made dark, however
a value of 'true' will not override any 'false' value in its parent chain nor will
it prevent any 'false' in any of its children. -->
<attr name="forceDarkAllowed" format="boolean" />
</declare-styleable>
<declare-styleable name="Theme">
<!-- <p>Whether or not the force dark feature is allowed to be applied to this theme.
<p>Setting this to false will disable the auto-dark feature on everything this
theme is applied to along with anything drawn by any children of views using
this theme.
<p>Setting this to true will allow this view to be automatically made dark, however
a value of 'true' will not override any 'false' value in its parent chain nor will
it prevent any 'false' in any of its children. -->
<attr name="forceDarkAllowed" format="boolean" />
</declare-styleable>
一個(gè)是 View 級(jí)別的即纲,一個(gè)是 Theme 級(jí)別的具帮。
4.1. Theme 級(jí)別 forceDarkAllowed
從上面的總結(jié)來看,Theme 級(jí)別的開關(guān)優(yōu)先級(jí)是最高的低斋,控制粒度也最大匕坯,我們看看源碼里面使用它的地方
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void updateForceDarkMode() {
// 渲染線程為空,直接返回
if (mAttachInfo.mThreadedRenderer == null) return;
// 系統(tǒng)是否打開了黑暗模式
boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
if (useAutoDark) {
// forceDarkAllowed 默認(rèn)值拔稳,開發(fā)者模式是否打開了強(qiáng)制 smart dark 選項(xiàng)
boolean forceDarkAllowedDefault =
SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
// useAutoDark = 使用淺色主題 && 主題中聲明的 forceDarkAllowed 值
useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
&& a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
a.recycle();
}
// 關(guān)鍵代碼,設(shè)置是否強(qiáng)制夜間模式
if (mAttachInfo.mThreadedRenderer.setForceDark(useAutoDark)) {
// TODO: Don't require regenerating all display lists to apply this setting
invalidateWorld(mView);
}
}
// frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
public boolean setForceDark(boolean enable) {
if (mForceDark != enable) {
mForceDark = enable;
// native 代碼锹雏,mNativeProxy 其實(shí)是 RenderThread 代理類的指針
nSetForceDark(mNativeProxy, enable);
return true;
}
return false;
}
這段代碼還是比較簡單巴比,判斷系統(tǒng):
- 是否打開了夜間模式
- 是否使用淺色主題
- Theme_forceDarkAllowed 是否為 true
三者同時(shí)為 true 時(shí)才會(huì)設(shè)置夜間模式,而 updateForceDarkMode 調(diào)用的時(shí)機(jī)分別是在 ViewRootImpl#setView
和 ViewRootImpl#updateConfiguration
礁遵,也就是初始化和夜間模式切換的時(shí)候都會(huì)調(diào)用轻绞,確保夜間模式能及時(shí)啟用和關(guān)閉。繼續(xù)跟蹤 HardwareRenderer#setForceDark
發(fā)現(xiàn)佣耐,這是一個(gè) native 方法政勃,所以接下來讓我們進(jìn)入 native 世界,nSetForceDark 對(duì)應(yīng)的實(shí)現(xiàn)位于
// frameworks/base/core/jni/android_view_ThreadedRenderer.cpp
static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,
jlong proxyPtr, jboolean enable) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
proxy->setForceDark(enable);
}
// frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
void RenderProxy::setForceDark(bool enable) {
mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
}
// frameworks/base/libs/hwui/renderthread/CanvasContext.h
class CanvasContext : public IFrameCallback {
public:
...
void setForceDark(bool enable) { mUseForceDark = enable; }
bool useForceDark() {
return mUseForceDark;
}
...
private:
...
// 默認(rèn)關(guān)閉強(qiáng)制夜間模式
bool mUseForceDark = false;
...
};
最終就是設(shè)置了一個(gè) CanvasContext 的變量值而已兼砖,什么都還沒有做奸远,那么這個(gè)變量值的作用是什么既棺,什么時(shí)候生效呢?我們進(jìn)一步查看使用的地方:
// frameworks/base/libs/hwui/TreeInfo.cpp
TreeInfo::TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext)
: mode(mode)
, prepareTextures(mode == MODE_FULL)
, canvasContext(canvasContext)
, damageGenerationId(canvasContext.getFrameNumber())
// 初始化 TreeInfo 的 disableForceDark 變量懒叛,注意變量值意義的變化丸冕,0 代表打開夜間模式,>0 代表關(guān)閉夜間模式
, disableForceDark(canvasContext.useForceDark() ? 0 : 1)
, screenSize(canvasContext.getNextFrameSize()) {}
}
進(jìn)一步看看 disableForceDark 使用的地方
// frameworks/base/libs/hwui/RenderNode.cpp
/**
* 這個(gè)可以說是核心方法了薛窥,handleForceDark 方法調(diào)用棧如下:
* - RenderNode#prepareTreeImpl
* - RenderNode#pushStagingDisplayListChanges
* - RenderNode#syncDisplayList
* - RenderNode#handleForceDark
*
* 而 RenderNode#prepareTree 是繪制的必經(jīng)之路胖烛,每一個(gè)節(jié)點(diǎn)都會(huì)走一遍這個(gè)流程
*/
void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
// 若沒打開強(qiáng)制夜間模式,直接退出
if (CC_LIKELY(!info || info->disableForceDark)) {
return;
}
// 根據(jù)是否有文字诅迷、是否有子節(jié)點(diǎn)佩番、子節(jié)點(diǎn)數(shù)量等情況,得出當(dāng)前 Node 屬于 Foreground 還是 Background
auto usage = usageHint();
const auto& children = mDisplayList->mChildNodes;
if (mDisplayList->hasText()) {
usage = UsageHint::Foreground;
}
if (usage == UsageHint::Unknown) {
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);
}
}
// 根據(jù) UsageHint 設(shè)置變色策略:Dark(壓暗)罢杉、Light(提亮)
mDisplayList->mDisplayList.applyColorTransform(
usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);
}
// frameworks/base/libs/hwui/RecordingCanvas.cpp
void DisplayListData::applyColorTransform(ColorTransform transform) {
// transform: Dark 或 Light
// color_transform_fns 是一個(gè)對(duì)應(yīng)所有繪制指令的函數(shù)指針數(shù)組趟畏,主要是對(duì) op 的 paint 變色或?qū)?bitmap 添加 colorfilter
this->map(color_transform_fns, transform);
}
template <typename Fn, typename... Args>
inline void DisplayListData::map(const Fn fns[], Args... args) const {
auto end = fBytes.get() + fUsed;
// 遍歷當(dāng)前的繪制的 op
for (const uint8_t* ptr = fBytes.get(); ptr < end;) {
auto op = (const Op*)ptr;
auto type = op->type;
auto skip = op->skip;
// 根據(jù) type 找到對(duì)應(yīng)的 fn,根據(jù)調(diào)用關(guān)系屑那,我們知道 fns 數(shù)組對(duì)應(yīng) color_transform_fns拱镐,這個(gè)數(shù)組其實(shí)是一個(gè)函數(shù)指針數(shù)組,下面看看定義
if (auto fn = fns[type]) { // We replace no-op functions with nullptrs
// 執(zhí)行
fn(op, args...); // to avoid the overhead of a pointless call.
}
ptr += skip;
}
}
#define X(T) colorTransformForOp<T>(),
static const color_transform_fn color_transform_fns[] = {
X(Flush)
X(Save)
X(Restore)
X(SaveLayer)
X(SaveBehind)
X(Concat)
X(SetMatrix)
X(Translate)
X(ClipPath)
X(ClipRect)
X(ClipRRect)
X(ClipRegion)
X(DrawPaint)
X(DrawBehind)
X(DrawPath)
X(DrawRect)
X(DrawRegion)
X(DrawOval)
X(DrawArc)
X(DrawRRect)
X(DrawDRRect)
X(DrawAnnotation)
X(DrawDrawable)
X(DrawPicture)
X(DrawImage)
X(DrawImageNine)
X(DrawImageRect)
X(DrawImageLattice)
X(DrawTextBlob)
X(DrawPatch)
X(DrawPoints)
X(DrawVertices)
X(DrawAtlas)
X(DrawShadowRec)
X(DrawVectorDrawable)
};
#undef X
color_transform_fn 宏定義展開
template <class T>
constexpr color_transform_fn colorTransformForOp() {
if
// op 變量中是否同時(shí)包含 paint 及 palette 屬性持际,若同時(shí)包含沃琅,則是繪制 Image 或者 VectorDrawable 的指令
// 參考:frameworks/base/libs/hwui/RecordingCanvas.cpp 中各 Op 的定義
constexpr(has_paint<T> && has_palette<T>) {
return [](const void* opRaw, ColorTransform transform) {
const T* op = reinterpret_cast<const T*>(opRaw);
// 關(guān)鍵變色方法,根據(jù) palette 疊加 colorfilter
transformPaint(transform, const_cast<SkPaint*>(&(op->paint)), op->palette);
};
}
else if
// op 變量中是否包含 paint 屬性蜘欲,普通繪制指令
constexpr(has_paint<T>) {
return [](const void* opRaw, ColorTransform transform) {
const T* op = reinterpret_cast<const T*>(opRaw);
// 關(guān)鍵變色方法益眉,對(duì) paint 顏色進(jìn)行變換
transformPaint(transform, const_cast<SkPaint*>(&(op->paint)));
};
}
else {
// op 變量不包含 paint 屬性,返回空
return nullptr;
}
}
static const color_transform_fn color_transform_fns[] = {
// 根據(jù) Flush姥份、Save郭脂、DrawImage等不同繪制 op,返回不同的函數(shù)指針
colorTransformForOp<Flush>
...
};
讓我們?cè)僖淮慰纯?map 方法
template <typename Fn, typename... Args>
inline void DisplayListData::map(const Fn fns[], Args... args) const {
auto end = fBytes.get() + fUsed;
for (const uint8_t* ptr = fBytes.get(); ptr < end;) {
auto op = (const Op*)ptr;
auto type = op->type;
auto skip = op->skip;
if (auto fn = fns[type]) { // We replace no-op functions with nullptrs
// 對(duì) op 的 paint 進(jìn)行顏色變換或疊加 colorfilter
fn(op, args...); // to avoid the overhead of a pointless call.
}
ptr += skip;
}
}
貼了一大段代碼澈歉,雖然代碼中已經(jīng)包含了注釋展鸡,但還是可能比較暈,我們先來整理下:
- CanvasContext.mUseForceDark 只會(huì)影響 TreeInfo.disableForceDark 的初始化
- TreeInfo.disableForceDark 若大于 0埃难,RenderNode 在執(zhí)行 handleForceDark 就會(huì)直接退出
- handleForceDark 方法里會(huì)根據(jù) UsageHint 類型莹弊,對(duì)所有 op 中的 paint 顏色進(jìn)行變換,如果是繪制圖片涡尘,則疊加一個(gè)反轉(zhuǎn)的 colorfilter忍弛。變換策略有:Dark、Light
接下來讓我們來看 paint 和 colorfilter 的變色實(shí)現(xiàn)
bool transformPaint(ColorTransform transform, SkPaint* paint) {
applyColorTransform(transform, *paint);
return true;
}
static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
if (transform == ColorTransform::None) return;
// 對(duì)畫筆顏色進(jìn)行顏色變換
SkColor newColor = transformColor(transform, paint.getColor());
paint.setColor(newColor);
if (paint.getShader()) {
SkShader::GradientInfo info;
std::array<SkColor, 10> _colorStorage;
std::array<SkScalar, _colorStorage.size()> _offsetStorage;
info.fColorCount = _colorStorage.size();
info.fColors = _colorStorage.data();
info.fColorOffsets = _offsetStorage.data();
SkShader::GradientType type = paint.getShader()->asAGradient(&info);
if (info.fColorCount <= 10) {
switch (type) {
case SkShader::kLinear_GradientType:
for (int i = 0; i < info.fColorCount; i++) {
// 對(duì) shader 中的顏色進(jìn)行顏色變換
info.fColors[i] = transformColor(transform, info.fColors[i]);
}
paint.setShader(SkGradientShader::MakeLinear(info.fPoint, info.fColors,
info.fColorOffsets, info.fColorCount,
info.fTileMode, info.fGradientFlags, nullptr));
break;
default:break;
}
}
}
if (paint.getColorFilter()) {
SkBlendMode mode;
SkColor color;
// TODO: LRU this or something to avoid spamming new color mode filters
if (paint.getColorFilter()->asColorMode(&color, &mode)) {
// 對(duì) colorfilter 中的顏色進(jìn)行顏色變換
color = transformColor(transform, color);
paint.setColorFilter(SkColorFilter::MakeModeFilter(color, mode));
}
}
}
邏輯很簡單考抄,就是對(duì)顏色進(jìn)行變換细疚,進(jìn)一步看看變色邏輯:
// 提亮顏色
static SkColor makeLight(SkColor color) {
// 轉(zhuǎn)換成 Lab 模式
Lab lab = sRGBToLab(color);
// 對(duì)明度進(jìn)行反轉(zhuǎn),明度越高川梅,反轉(zhuǎn)后越低
float invertedL = std::min(110 - lab.L, 100.0f);
if (invertedL > lab.L) {
// 反轉(zhuǎn)后的明度高于原明度疯兼,則使用反轉(zhuǎn)后的明度
lab.L = invertedL;
return LabToSRGB(lab, SkColorGetA(color));
} else {
return color;
}
}
// 壓暗顏色
static SkColor makeDark(SkColor color) {
// 轉(zhuǎn)換成 Lab 模式
Lab lab = sRGBToLab(color);
// 對(duì)明度進(jìn)行反轉(zhuǎn)然遏,明度越高,反轉(zhuǎn)后越低
float invertedL = std::min(110 - lab.L, 100.0f);
if (invertedL < lab.L) {
// 反轉(zhuǎn)后的明度低于原明度镇防,則使用反轉(zhuǎn)后的明度
lab.L = invertedL;
// 使用 rgb 格式返回
return LabToSRGB(lab, SkColorGetA(color));
} else {
// 直接返回原顏色
return color;
}
}
static SkColor transformColor(ColorTransform transform, SkColor color) {
switch (transform) {
case ColorTransform::Light:
return makeLight(color);
case ColorTransform::Dark:
return makeDark(color);
default:
return color;
}
}
到此啦鸣,對(duì) paint 的變換結(jié)束,看來無非就是反轉(zhuǎn)明度来氧。
再來看看對(duì)圖片的變換:
bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) {
// 根據(jù) palette 和 colorfilter 判斷圖片是亮還是暗的
palette = filterPalette(paint, palette);
bool shouldInvert = false;
if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) {
// 圖片本身是亮的诫给,但是要求變暗,反轉(zhuǎn)
shouldInvert = true;
}
if (palette == BitmapPalette::Dark && transform == ColorTransform::Light) {
// 圖片本身是暗的啦扬,但是要求變亮中狂,反轉(zhuǎn)
shouldInvert = true;
}
if (shouldInvert) {
SkHighContrastConfig config;
config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;
// 疊加一個(gè)亮度反轉(zhuǎn)的 colorfilter
paint->setColorFilter(SkHighContrastFilter::Make(config)->makeComposed(paint->refColorFilter()));
}
return shouldInvert;
}
終于,bitmap 的變換也分析完了扑毡,呼~
4.2. View 級(jí)別 forceDarkAllowed
但是胃榕,還沒完呢~~~還記得我們最開始說的,除了 Theme 級(jí)別瞄摊,還有一個(gè) View 級(jí)別的 forceDarkAllowed勋又,通過 View 級(jí)別 forceDarkAllowed 可以關(guān)掉它及它的子 view 的夜間模式開關(guān)。依然從 java 層看下去哈
// rameworks/base/core/java/android/view/View.java
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.View_forceDarkAllowed:
// 注意换帜,這個(gè)默認(rèn)是 true 的
mRenderNode.setForceDarkAllowed(a.getBoolean(attr, true));
break;
}
}
}
}
// frameworks/base/graphics/java/android/graphics/RenderNode.java
public final class RenderNode {
public boolean setForceDarkAllowed(boolean allow) {
// 又是 native 方法
return nSetAllowForceDark(mNativeRenderNode, allow);
}
}
// frameworks/base/core/jni/android_view_RenderNode.cpp
static jboolean android_view_RenderNode_setAllowForceDark(jlong renderNodePtr, jboolean allow) {
return SET_AND_DIRTY(setAllowForceDark, allow, RenderNode::GENERIC);
}
// frameworks/base/libs/hwui/RenderProperties.h
class ANDROID_API RenderProperties {
public:
bool setAllowForceDark(bool allow) {
// 設(shè)置到 mPrimitiveFields.mAllowForceDark 變量中
return RP_SET(mPrimitiveFields.mAllowForceDark, allow);
}
bool getAllowForceDark() const {
return mPrimitiveFields.mAllowForceDark;
}
}
和 Theme 級(jí)別的一樣楔壤,僅僅只是設(shè)置到變量中而已,關(guān)鍵是要看哪里使用這個(gè)變量惯驼,經(jīng)過查找蹲嚣,我們發(fā)現(xiàn),它的使用同樣在 RenderNode 的 prepareTreeImpl 中:
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
...
// 1. 如果 view 關(guān)閉了夜間模式祟牲,會(huì)在這里讓 info.disableForceDark 加 1
// 2. info.disableForceDark 正是 handleForceDark 中關(guān)鍵變量隙畜,還記得嗎?
// 3. nfo.disableForceDark 大于 0 會(huì)讓此 RenderNode 跳過夜間模式處理
// 4. 如果 info.disableForceDark 本身已經(jīng)大于 0 了说贝,view.setForceDarkAllowed(true) 也毫無意義
if (!mProperties.getAllowForceDark()) {
info.disableForceDark++;
}
prepareLayer(info, animatorDirtyMask);
if (info.mode == TreeInfo::MODE_FULL) {
// 這里面會(huì)調(diào)用 handleForceDark 方法處理夜間模式
pushStagingDisplayListChanges(observer, info);
}
if (mDisplayList) {
info.out.hasFunctors |= mDisplayList->hasFunctor();
// 遞歸調(diào)用子 Node 的 prepareTreeImpl 方法
bool isDirty = mDisplayList->prepareListAndChildren(
observer, info, childFunctorsNeedLayer,
[](RenderNode* child, TreeObserver& observer, TreeInfo& info,
bool functorsNeedLayer) {
child->prepareTreeImpl(observer, info, functorsNeedLayer);
});
if (isDirty) {
damageSelf(info);
}
}
...
// 重要议惰,把 info.disableForceDark 恢復(fù)回原來的值,不讓它影響 Tree 中同級(jí)的其他 RenderNode
// 但是本 RenderNode 的子節(jié)點(diǎn)還是會(huì)受影響的乡恕,這就是為什么父 view 關(guān)閉了夜間模式言询,子 view 也會(huì)受影響的原因
// 因?yàn)檫€原 info.disableForceDark 操作是在遍歷子節(jié)點(diǎn)之后執(zhí)行的
if (!mProperties.getAllowForceDark()) {
info.disableForceDark--;
}
...
}
5. 總結(jié)
本文到目前為止,總算把 Android Q 夜間模式實(shí)現(xiàn)原理梳理了一遍几颜,總的來說實(shí)現(xiàn)不算復(fù)雜,說白了就是把 paint 中的顏色轉(zhuǎn)換一下或者疊加一個(gè) colorfilter讯屈,雖然中間還有關(guān)聯(lián)知識(shí)沒有細(xì)說蛋哭,如 RenderThread、DisplayList涮母、RenderNode 等圖形相關(guān)的概念谆趾,限于文章大小躁愿,請(qǐng)讀者自行了解
另外,由于水平有限沪蓬,難免文中有錯(cuò)漏之處彤钟,若哪里寫的不對(duì),請(qǐng)大家及時(shí)指出跷叉,蟹蟹啦~