Android Q深色模式及源码解析,2024年最新计算机毕业项目成果

3.1.应用主题继承 DayNight 主题

Android其实的Support包从23.2版本就开始支持夜间模式,也就是DayNight Mode。接下来我们就讲讲如何从uimode的角度去适配

  • 继承AppCompatActivity

  • 使用Theme.AppCompat.DayNight这个Theme,例如如果你之前的应用Theme是:

Theme.AppCompat.NoActionBar

改为:

Theme.AppCompat.DayNight.NoActionBar

继承后,如果当前开启了夜间模式,系统会自动从 night-qualified 中加载资源,所以应用的颜色、图标等资源应尽量避免硬编码,而是推荐使用新增 attributes 指向不同的资源,如:

?android:attr/textColorPrimary?attr/colorControlNormal

附上一张Material Design经典的颜色设置指导图:

如何自定义深色模式下的样式

首先在res下,创建一个新的values-night目录,然后在该目录下新建一个styles.xml,把夜间模式的颜色在这里设置下。

这样在切换到深色模式时就会使用values-night的资源。你不用把整个styles.xml的内容都复制过来,例如你只想改colorPrimary,你就设置一个colorPrimary就行了。

当然了,不只是styles.xmlcolors.xmldimens.xmlstrings.xml都可以这么做。意思这些资源调用都是就近原则,切换到夜间模式时values-night有就使用values-night的,没有还是使用values的。

应用内主动切换白天、深色模式

如果希望可以在应用内主动切换白天、深色模式,在Application中设置初始化一下Theme。例如像这样:

static {

AppCompatDelegate.setDefaultNightMode(

AppCompatDelegate.MODE_NIGHT_YES);

}

然后改变Theme的方法就是

getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);

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 ThemeforceDarkAllowed 是不会生效的。

另外,如果你不希望某个 view 被强制夜间模式处理,则可以给 view 添加 android:forceDarkAllowed="false" 或者 view.setForceDarkAllowed(false),设置之后,即使打开了夜间模式且主题添加了 forceDarkAllowed,该 view 也不会变深色。比较重要的一点是,这个接口只能关闭夜间模式,不能开启夜间模式,也就是说,如果主题中没有显示声明 forceDarkAllowedview.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;

}

最终发现,这是一个 native 方法,nSetForceDark的真正实现是在ThreadedRenderer.cpp

//frameworks/base/core/jni/android_view_ThreadedRenderer.cpp

static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,

jlong proxyPtr, jboolean enable) {

//将proxyPtr强转成RenderProxy

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

proxy->setForceDark(enable);

}

RenderProxy是一个代理类,也是MainThreadRenderThread通信的桥梁。关于MainThreadRenderThread的概念,后面会再单独讲述,这里不作展开。

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

void RenderProxy::setForceDark(bool enable) {

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

}

这里从MainThread post了一个调用CanvasContext成员函数setForceDark的任务到RenderThread渲染线程

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

void setForceDark(bool enable) {

mUseForceDark = enable;

}

bool useForceDark() {

return mUseForceDark;

}

发现只是设置了一个mUseForceDark变量而已,并没有看到关键性的调用。我们只能继续再跟一下mUseForceDark这个变量在哪里使用到了。最终发现,是在TreeInfo中被赋值给disableForceDark变量

//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 代表关闭夜间模式

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

, screenSize(canvasContext.getNextFrameSize()) {}

} // namespace android::uirenderer

而最终disableForceDark是在RenderNode中使用,调用路径为:prepareTree-->prepareTreeImpl-->pushStagingDisplayListChanges-->syncDisplayList-->handleForceDark

而最核心当属handleForceDark函数:

//frameworks/base/libs/hwui/RenderNode.cpp

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

// // 若没打开强制夜间模式,直接退出

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

return;

}

auto usage = usageHint();

const auto& children = mDisplayList->mChildNodes;

//根据是否有文字、是否有子节点、子节点数量等情况,得出当前 Node 属于 Foreground 还是 Background

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

}

}

//根据 UsageHint 设置变色策略:Dark(压暗)、Light(提亮)

mDisplayList->mDisplayList.applyColorTransform(

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

}

//frameworks/base/libs/hwui/RecordingCanvas.cpp

// frameworks/base/libs/hwui/RecordingCanvas.cpp

void DisplayListData::applyColorTransform(ColorTransform transform) {

// transform: Dark 或 Light

// color_transform_fns 是一个对应所有绘制指令的函数指针数组,主要是对 op 的 paint 变色或对 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;

// 遍历当前的绘制的 op

for (const uint8_t* ptr = fBytes.get(); ptr < end;) {

auto op = (const Op*)ptr;

auto type = op->type;

auto skip = op->skip;

// 根据 type 找到对应的 fn,根据调用关系,我们知道 fns 数组对应 color_transform_fns,这个数组其实是一个函数指针数组,下面看看定义

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;

}

}

#define X(T) colorTransformForOp(),

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

constexpr color_transform_fn colorTransformForOp() {

if

// op 变量中是否同时包含 paint 及 palette 属性,若同时包含,则是绘制 Image 或者 VectorDrawable 的指令

// 参考:frameworks/base/libs/hwui/RecordingCanvas.cpp 中各 Op 的定义

constexpr(has_paint && has_palette) {

return [](const void* opRaw, ColorTransform transform) {

const T* op = reinterpret_cast<const T*>(opRaw);

// 关键变色方法,根据 palette 叠加 colorfilter

transformPaint(transform, const_cast<SkPaint*>(&(op->paint)), op->palette);

};

}

else if

// op 变量中是否包含 paint 属性,普通绘制指令

constexpr(has_paint) {

return [](const void* opRaw, ColorTransform transform) {

const T* op = reinterpret_cast<const T*>(opRaw);

// 关键变色方法,对 paint 颜色进行变换

transformPaint(transform, const_cast<SkPaint*>(&(op->paint)));

};

}

else {

// op 变量不包含 paint 属性,返回空

return nullptr;

}

}

static const color_transform_fn color_transform_fns[] = {

// 根据 Flush、Save、DrawImage等不同绘制 op,返回不同的函数指针

colorTransformForOp

};

让我们再一次看看 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

// 对 op 的 paint 进行颜色变换或叠加 colorfilter

fn(op, args…); // to avoid the overhead of a pointless call.

}

ptr += skip;

}

}

我们先来整理下:

  • CanvasContext.mUseForceDark 只会影响 TreeInfo.disableForceDark 的初始化

  • TreeInfo.disableForceDark 若大于 0RenderNode 在执行 handleForceDark就会直接退出

  • handleForceDark 方法里会根据 UsageHint 类型,对所有 op 中的 paint 颜色进行变换,如果是绘制图片,则叠加一个反转的 colorfilter。变换策略有:Dark、Light

接下来让我们来看 paint 和 colorfilter 的变色实现,这部分是交由CanvasTransform中处理:

bool transformPaint(ColorTransform transform, SkPaint* paint) {

applyColorTransform(transform, *paint);

return true;

}

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) {

case SkShader::kLinear_GradientType:

for (int i = 0; i < info.fColorCount; i++) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。

因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
img
img
img

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
img

rStorage.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++) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。

因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
[外链图片转存中…(img-30VO6799-1712510752989)]
[外链图片转存中…(img-OgdnZ60N-1712510752989)]
[外链图片转存中…(img-Vz8CYdQq-1712510752990)]

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
[外链图片转存中…(img-JpuD0nDp-1712510752990)]

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值