2024年最全【阿里P8大牛一篇文章教你】Android-深色模式适配原理分析(1),字节跳动Andorid岗25k+的面试题

建议

当我们出去找工作,或者准备找工作的时候,我们一定要想,我面试的目标是什么,我自己的技术栈有哪些,近期能掌握的有哪些,我的哪些短板 ,列出来,有计划的去完成,别看前两天掘金一些大佬在驳来驳去 ,他们的观点是他们的,不要因为他们的观点,膨胀了自己,影响自己的学习节奏。基础很大程度决定你自己技术层次的厚度,你再熟练框架也好,也会比你便宜的,性价比高的替代,很现实的问题但也要有危机意识,当我们年级大了,有哪些亮点,与比我们经历更旺盛的年轻小工程师,竞争。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!!!!!!!

  • 准备想说怎么样写简历,想象算了,我觉得,技术就是你最好的简历

  • 我希望每一个努力生活的it工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

  • 有什么问题想交流,欢迎给我私信,欢迎评论

【附】相关架构及资料

Android高级技术大纲

面试资料整理

内含往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

对于机器小人打印的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_1color_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。

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

  1. 要强制深色模式生效必须开启硬件加速(默认开启)
  2. 主题设置的Force Dark仅对Light的主题有效,对非Light的主题不管是设置android:forceDarkAllowedtrue或者设置View.setForceDarkAllowed(true)都是无效的。
  3. 父节点设置了不支持Force Dark,那么子节点再设置支持Force Dark无效。例如主题设置了android:forceDarkAllowedfalse,则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变量会判断当前主题是否为浅色,因此可以得到第二个结论:强制深色模式只在浅色主题下生效。直到这一步的调用链如下:[图片上传失败…(image-64fcc1-1603115470872)]

mAttachInfo.mThreadedRendererThreadRenderer,继承自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()过程中,到达的流程如下图:

[图片上传失败…(image-61d4ce-1603115470871)]

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);
}

Tips:View的绘制会根据VSYNC信号,将UI线程的Display List树同步到Render线程的Display List树,并通过生产者消费者模式将layout信息放置到SurfaceFlinger中,并最后交给Haredware Composer进行合成绘制。具体View渲染逻辑见参考章节的15~19文章列表。 frameworks/base/libs/hwui/RecordingCanvas.cpp

void DisplayListData::applyColorTransform(ColorTransform transform) {
// 使用transform作为参数执行color_transform_fns函数组
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;
// 遍历需要绘制的元素op,并调用对应类型的colorTransformForOp函数
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
fn(op, args…); // to avoid the overhead of a pointless call.
}
ptr += skip;
}
}

typedef void (color_transform_fn)(const void, ColorTransform);

#define X(T) colorTransformForOp(),
static const color_transform_fn color_transform_fns[] = {
// 相当于 colorTransformForOp()
X(Flush)
X(Save)
X(Restore)
X(SaveLayer)
X(SaveBehind)
X(Concat44)
X(Concat)
X(SetMatrix)
X(Scale)
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)
X(DrawWebView)
};
#undef X

struct DrawImage final : Op {
static const auto kType = Type::DrawImage;
DrawImage(sk_sp&& image, SkScalar x, SkScalar y, const SkPaint* paint,
BitmapPalette palette)
: image(std::move(image)), x(x), y(y), palette(palette) {
if (paint) {
this->paint = paint;
}
}
sk_sp image;
SkScalar x, y;
// 这里SK指代skia库对象
SkPaint paint;
BitmapPalette palette;
void draw(SkCanvas
c, const SkMatrix&) const { c->drawImage(image.get(), x, y, &paint); }
};

template
constexpr color_transform_fn colorTransformForOp() {
if
// 如果类型T有paint变量,并且有palette变量
constexpr(has_paint && has_palette) {
// It’s a bitmap(绘制Bitmap)
// 例如对于一个DrawImage的OP,最终会调用到这里
// opRaw对应DrawImage对象,transform为ColorTransform::Dark或者ColorTransform::Light
return [](const void* opRaw, ColorTransform transform) {
// TODO: We should be const. Or not. Or just use a different map
// Unclear, but this is the quick fix
const T* op = reinterpret_cast<const T*>(opRaw);
transformPaint(transform, const_cast<SkPaint*>(&(op->paint)), op->palette);
};
}
else if
constexpr(has_paint) {
return [](const void* opRaw, ColorTransform transform) {
// TODO: We should be const. Or not. Or just use a different map
// Unclear, but this is the quick fix
// 非Bitmap绘制
const T* op = reinterpret_cast<const T*>(opRaw);
transformPaint(transform, const_cast<SkPaint*>(&(op->paint)));
};
}
else {
return nullptr;
}
}

frameworks/base/libs/hwui/CanvasTransform.cpp

这里进行具体的颜色转换逻辑,我们首先关注非Bitmap绘制的颜色转换

// 非Bitmap绘制颜色模式转换
bool transformPaint(ColorTransform transform, SkPaint* paint) {
applyColorTransform(transform, *paint);
return true;
}

// 非Bitmap绘制颜色模式转换
static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
if (transform == ColorTransform::None) return;
// 具体绘制颜色转换逻辑
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) {
// 线性渐变并且渐变颜色少于等于10个的情况
case SkShader::kLinear_GradientType:
for (int i = 0; i < info.fColorCount; i++) {
// 对渐变色颜色进行转换
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;
}

}
}

// 处理colorFilter
if (paint.getColorFilter()) {
SkBlendMode mode;
SkColor color;
// TODO: LRU this or something to avoid spamming new color mode filters
if (paint.getColorFilter()->asAColorMode(&color, &mode)) {
// 对colorFilter颜色进行转换
color = transformColor(transform, color);
paint.setColorFilter(SkColorFilters::Blend(color, mode));
}
}
}

static SkColor transformColor(ColorTransform transform, SkColor color) {
switch (transform) {
case ColorTransform::Light:
return makeLight(color);
case ColorTransform::Dark:
return makeDark(color);
default:
return color;
}
}

// 前景色变亮
static SkColor makeLight(SkColor color) {
// 将sRGB色彩模式转换成Lab色彩模式
Lab lab = sRGBToLab(color);
// 对亮度L维度取反
float invertedL = std::min(110 - lab.L, 100.0f);
if (invertedL > lab.L) {
// 若取反后亮度变亮,则替换原来亮度
lab.L = invertedL;
// 重新转换为sRGB模式
return LabToSRGB(lab, SkColorGetA(color));
} else {
return color;
}
}

// 后景色变暗
static SkColor makeDark(SkColor color) {
// 将sRGB色彩模式转换成Lab色彩模式
Lab lab = sRGBToLab(color);
// 对亮度L维度取反
float invertedL = std::min(110 - lab.L, 100.0f);
if (invertedL < lab.L) {
// 若取反后亮度变暗,则替换原来亮度
lab.L = invertedL;
// 重新转换为sRGB模式
return LabToSRGB(lab, SkColorGetA(color));
} else {
return color;
}
}

从代码中可以看出,深色模式应用之后,通过对sRGB色彩空间转换Lab色彩空间,并对表示亮度的维度L进行取反,并判断取反后前景色是不是更亮,后景色是不是更暗,若是的话就替换为原来的L,并再重新转换为sRGB色彩空间,从而实现反色的效果。

我们再来看对图片的强制深色模式处理:

// Bitmap绘制颜色模式转换
bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) {
// 考虑加上filter之后图片的明暗
palette = filterPalette(paint, palette);
bool shouldInvert = false;
if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) {
// 图片比较亮但是需要变暗
shouldInvert = true;
}
if (palette == BitmapPalette::Dark && transform == ColorTransform::Light) {
// 图片比较暗但是需要变亮
shouldInvert = true;
}
if (shouldInvert) {
SkHighContrastConfig config;
// 设置skia反转亮度的filter
config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;
paint->setColorFilter(SkHighContrastFilter::Make(config)->makeComposed(paint->refColorFilter()));
}
return shouldInvert;
}

// 获取paint filter的palette值,若没有filter直接返回原来的palette
static BitmapPalette filterPalette(const SkPaint* paint, BitmapPalette palette) {
// 如果没有filter color返回原来的palette
if (palette == BitmapPalette::Unknown || !paint || !paint->getColorFilter()) {
return palette;
}

SkColor color = palette == BitmapPalette::Light ? SK_ColorWHITE : SK_ColorBLACK;
// 获取filter color,并根据palette的明暗再叠加一层白色或者黑色
color = paint->getColorFilter()->filterColor(color);
// 根据将颜色转换为HSV空间,并返回是图片的亮度是亮还是暗
return paletteForColorHSV(color);
}

从代码中可以看出,对于Bitmap类型的绘制,先判断原来绘制Bitmap的明暗度,如果原来绘制的图像较为明亮但是需要变暗,或者原来绘制的图像较为暗需要变明亮,则设置一个明亮度转换的filter到画笔paint中。

至此,对于主题级别的强制深色转换原理已经非常清晰。总结一下,就是需要对前景色变亮和背景色变暗,然后对于非Bitmap类型明暗变化采用的是将色值转换为Lab颜色空间进行明亮度转换,对于Bitmap类型的明暗变化采取设置亮度转换的filter进行。

2. View

无论是设置View的xml的android:forceDarkAllowed属性,还是调用View.setForceDarkAllowed()最后还是调用到frameworks/base/core/java/android/view/View.java的mRenderNode.setForceDarkAllowed()方法。

frameworks/base/graphics/java/android/graphics/RenderNode.java

public boolean setForceDarkAllowed(boolean allow) {
return nSetAllowForceDark(mNativeRenderNode, allow);
}

nSetAllowForceDark通过JNI调用到android_view_RenderNode_setAllowForceDarkNavtive方法中。

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

static const JNINativeMethod gMethods[] = {
// …
{ “nSetAllowForceDark”, “(JZ)Z”, (void*) android_view_RenderNode_setAllowForceDark },
// …
};

static jboolean android_view_RenderNode_setAllowForceDark(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jboolean allow) {
return SET_AND_DIRTY(setAllowForceDark, allow, RenderNode::GENERIC);
}

#define SET_AND_DIRTY(prop, val, dirtyFlag)
(reinterpret_cast<RenderNode*>(renderNodePtr)->mutateStagingProperties().prop(val)
? (reinterpret_cast<RenderNode*>(renderNodePtr)->setPropertyFieldsDirty(dirtyFlag), true)
: false)

最后这个是否允许深色模式的allow变量被设置到RenderProperties.h 中frameworks/base/libs/hwui/RenderProperties.h

/*

  • Data structure that holds the properties for a RenderNode
    */
    class ANDROID_API RenderProperties {
    public:
    // …
    // 设置View是否允许强制深色模式
    bool setAllowForceDark(bool allow) {
    return RP_SET(mPrimitiveFields.mAllowForceDark, allow);
    }
    // 获取View是否允许强制深色模式
    bool getAllowForceDark() const {
    return mPrimitiveFields.mAllowForceDark;
    }
    // …
    private:
    // Rendering properties
    struct PrimitiveFields {
    // …
    // 默认值为true
    bool mAllowForceDark = true;
    // …
    } mPrimitiveFields;

我们回头看下上面分析过的RenderNode.cppprepareTreeImpl流程frameworks/base/libs/hwui/RenderNode.cpp

// 经过了简化处理的prepareTreeImpl逻辑
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info) {
// 如果当前View不允许被ForceDark,那么info.disableForceDark值+1
if (!mProperties.getAllowForceDark()) {
info.disableForceDark++;
}

最后

**一个零基础的新人,我认为坚持是最最重要的。**我的很多朋友都找我来学习过,我也很用心的教他们,可是不到一个月就坚持不下来了。我认为他们坚持不下来有两点主要原因:

他们打算入行不是因为兴趣,而是因为所谓的IT行业工资高,或者说完全对未来没有任何规划。

刚开始学的时候确实很枯燥,这确实对你是个考验,所以说坚持下来也很不容易,但是如果你有兴趣就不会认为这是累,不会认为这很枯燥,总之还是贵在坚持。

技术提升遇到瓶颈了?缺高级Android进阶视频学习提升自己吗?还有大量大厂面试题为你面试做准备!

提升自己去挑战一下BAT面试难关吧

对于很多Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

不论遇到什么困难,都不应该成为我们放弃的理由!

如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!

最后祝各位新人都能坚持下来,学有所成。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

过,我也很用心的教他们,可是不到一个月就坚持不下来了。我认为他们坚持不下来有两点主要原因:

他们打算入行不是因为兴趣,而是因为所谓的IT行业工资高,或者说完全对未来没有任何规划。

刚开始学的时候确实很枯燥,这确实对你是个考验,所以说坚持下来也很不容易,但是如果你有兴趣就不会认为这是累,不会认为这很枯燥,总之还是贵在坚持。

技术提升遇到瓶颈了?缺高级Android进阶视频学习提升自己吗?还有大量大厂面试题为你面试做准备!

提升自己去挑战一下BAT面试难关吧

[外链图片转存中…(img-MXjVrEMz-1715891610978)]

对于很多Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

不论遇到什么困难,都不应该成为我们放弃的理由!

如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!

最后祝各位新人都能坚持下来,学有所成。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值