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 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的设置有以下几个规则:
-
要强制深色模式生效必须开启硬件加速(默认开启)
-
主题设置的Force Dark仅对Light的主题有效,对非Light的主题不管是设置android:forceDarkAllowed为true或者设置View.setForceDarkAllowed(true)都是无效的。
-
父节点设置了不支持Force Dark,那么子节点再设置支持Force Dark无效。例如主题设置了android:forceDarkAllowed为false,则View设置View.setForceDarkAllowed(true)无效。同样的,如果View本身设置了支持Force Dark,但是其父layout设置了不支持,那么该View不会执行Force Dark
-
子节点设置不支持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);
}
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;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)
![](https://i-blog.csdnimg.cn/blog_migrate/b57664045be952d574a46b67ff9277cd.jpeg)
最后是今天给大家分享的一些独家干货:
【Android开发核心知识点笔记】
【Android思维脑图(技能树)】
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
【Android高级架构视频学习资源】
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
d(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;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-c7fbK05j-1711607122952)]
[外链图片转存中…(img-LmY2h72u-1711607122952)]
[外链图片转存中…(img-nRb7b0mX-1711607122953)]
[外链图片转存中…(img-jiBig4In-1711607122953)]
[外链图片转存中…(img-GqQt6xVO-1711607122953)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)
![](https://i-blog.csdnimg.cn/blog_migrate/b57664045be952d574a46b67ff9277cd.jpeg)
最后是今天给大家分享的一些独家干货:
【Android开发核心知识点笔记】
[外链图片转存中…(img-bZGqR4Iv-1711607122954)]
【Android思维脑图(技能树)】
[外链图片转存中…(img-9Pb54RLc-1711607122954)]
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
[外链图片转存中…(img-ZWSRxEeq-1711607122955)]
【Android高级架构视频学习资源】