Android 13 取色引擎详解
PixelLauncher的插件实现颜色动态变化,除了从Wallpaper中拿到颜色,还进行了二次加工,使其更符合Material Desgin的风格,这也是符合“Monet”主题系统 中的一部分,目前是没有开源的。所以一方或三方若想使用该特性,可以按需进行选择和二次加工。
1、 应用如何获取壁纸颜色?
1.1、调用getWallpaperColors获取壁纸颜色
其实WallpaperManager从很早之前就提供了getWallpaperColors接口
/frameworks/base/core/java/android/app/WallpaperManager.java
...
@UnsupportedAppUsage
public @Nullable WallpaperColors getWallpaperColors(int which, int userId) {
StrictMode.assertUiContext(mContext, "getWallpaperColors");
return sGlobals.getWallpaperColors(which, userId, mContext.getDisplayId());
}
...
WallpaperColors getWallpaperColors(int which, int userId, int displayId) {
if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
throw new IllegalArgumentException(
"Must request colors for exactly one kind of wallpaper");
}
try {
return mService.getWallpaperColors(which, userId, displayId);
} catch (RemoteException e) {
// Can't get colors, connection lost.
}
return null;
}
...
1.2、注册OnColorsChangedListener接口监听壁纸颜色
1.2.1、获取整个壁纸颜色
/frameworks/base/core/java/android/app/WallpaperManager.java
...
public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener,
@NonNull Handler handler) {
addOnColorsChangedListener(listener, handler, mContext.getUserId());
}
...
使用范例:
WallpaperManager wallpaperManager=(WallpaperManager) getSystemService(Context.WALLPAPER_SERVICE);
wallpaperManager.addOnColorsChangedListener(new WallpaperManager.OnColorsChangedListener() {
@Override
public void onColorsChanged(WallpaperColors colors, int which) {
}
});
1.2.2、获取壁纸区域颜色(需要源码引用)
/frameworks/base/core/java/android/app/WallpaperManager.java
...
/**
* @hide
*/
public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
List<RectF> regions) throws IllegalArgumentException {
for (RectF region : regions) {
if (!LOCAL_COLOR_BOUNDS.contains(region)) {
throw new IllegalArgumentException("Regions must be within bounds "
+ LOCAL_COLOR_BOUNDS);
}
}
sGlobals.addOnColorsChangedListener(callback, regions, FLAG_SYSTEM,
mContext.getUserId(), mContext.getDisplayId());
}
...
==Tip: LocalWallpaperColorConsumer是hide接口,非源码编译的Apk无法引用==
使用范例:
addOnColorsChangedListener(new LocalWallpaperColorConsumer() {
@java.lang.Override
public void onColorsChanged(RectF area, WallpaperColors colors) {
}
},handler);
1.3、使用WallpaperColors
取出的数据都封装在WallpaperColors中,可通过以下接口获取到原始数据
/frameworks/base/core/java/android/app/WallpaperColors.java
//获取墙纸最具视觉代表性的颜色。“视觉上具有代表性”是指在图像中很容易被注意到,可能发生在高频率。不为空
public @NonNull Color getPrimaryColor() {
return mMainColors.get(0);
}
//获取壁纸的第二个最杰出的颜色。可以为空
public @Nullable Color getSecondaryColor() {
return mMainColors.size() < 2 ? null : mMainColors.get(1);
}
//获得壁纸的第三个最杰出的颜色。可以为空
public @Nullable Color getTertiaryColor() {
return mMainColors.size() < 3 ? null : mMainColors.get(2);
}
//最杰出的颜色列表,按重要性排序
public @NonNull List<Color> getMainColors() {
return Collections.unmodifiableList(mMainColors);
}
//得到所有提取出来的颜色,key是颜色的rgb int值 ,value是出现次数
public @NonNull Map<Integer, Integer> getAllColors() {
return Collections.unmodifiableMap(mAllColors);
}
框架中只是负责提取出原始颜色,但有些时候直接使用原始颜色,并不能达到最好的效果。
例如:PixelLauncher的插件实现颜色动态变化,除了从Wallpaper中拿到颜色,还进行了二次加工,使其更符合Material Desgin的风格,这也是符合“Monet”主题系统 中的一部分,目前是没有开源的。所以一方或三方若想使用该特性,可以按需进行选择和二次加工。
2、根据壁纸取色,能取出多少种颜色,颜色的格式和使用场景是什么?
在Android 13上,内存低的机器 ,最多取出五种色值,内存高的机器,最多取出128种颜色。
/frameworks/base/core/java/android/app/WallpaperColors.java
...
public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap,
@FloatRange (from = 0f, to = 1f) float dimAmount) {
...
if (ActivityManager.isLowRamDeviceStatic()) {
palette = Palette
.from(bitmap, new VariationalKMeansQuantizer())
.maximumColorCount(5)
.resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
.generate();
} else {
palette = Palette
.from(bitmap, new CelebiQuantizer())
.maximumColorCount(128)
.resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
.generate();
}
...
- 取出的是一个WallpaperColors对象,其中包含三种主颜色,以及一个保存了所有提取出颜色的Color数组(Hide)。
- 目前的应用场景主要是PixelLauncher、SystemUI、GBoard
3、能从动态壁纸中取色吗?
很可惜,Google并没有为动态壁纸提供默认的取色逻辑。
壁纸的取色,最终会调用到WallpaperService的onComputeColors中,但该方法是空实现,框架只是提供了最基本的获取接口而已。
所以在Android 13之前,无论是静态壁纸或是动态 壁纸,若想要其他模块能获取到壁纸颜色的话,需要壁纸提供方,在实现WallpaperService的Engine时,也一并实现onCompleteColors方法,然后在颜色变化时调用notifyColorsChanged。
/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
...
public void notifyColorsChanged() {
...
try {
//关键调用
final WallpaperColors newColors = onComputeColors();
if (mConnection != null) {
//通知给监听壁纸颜色变化的模块
mConnection.onWallpaperColorsChanged(newColors, mDisplay.getDisplayId());
} else {
Log.w(TAG, "Can't notify system because wallpaper connection "
+ "was not established.");
}
...
} catch (RemoteException e) {
Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
}
}
//重写该方法
public @Nullable WallpaperColors onComputeColors() {
return null;
}
...
如图:
特性 | < Android 13 | Android 13 |
---|---|---|
静态壁纸 | 没有默认实现 | 提供了默认实现,可以根据区域取色 |
动态壁纸 | 空实现 | 空实现 |
4、壁纸取色失败会使用默认颜色吗?
壁纸其实是通过Palette接口进行颜色提取的,基本上都能取到至少一种颜色。当真的取出失败或取出的颜色如果不符合期望,需要采用另外一套默认颜色,这是由各个各个业务去处理的,比如Launcher中的LauncherAppWidgetHostView,重写了setColorResources方法
/packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
...
@Override
public void setColorResources(@Nullable SparseIntArray colors) {
if (colors == null) {
//关键点1,提取不到颜色,会重置插件的颜色
resetColorResources();
} else {
//关键点2,设置插件的颜色
super.setColorResources(colors);
}
}
...
5、Widget 取色的整体流程是怎样?
具体流程见下图:
PixelLauncher是通过LocalWallpaperColorsExtractor的实现类进行颜色提取,该类继承于LocalColorExtractor,该类存在于Launcher3中,是空实现。可以看出,Google专门将Monet算法部分折分隐藏,只公开框架部分。
而LocalWallpaperColorsExtractor是通过调用WallpaperManager.addOnColorsChangedListener接口,获取onColorsChanged(RectF rectF, WallpaperColors wallpaperColors)中的返回信息,然后再对WallpaperColors中的颜色信息进行二次加工处理。
6、Widget 能使用的颜色数量和范围
Widget能使用的色值数量没有限制,从android.R.color.system_neutrall_0(#FFFFFF) 到android.R.color.system_accent3_1000(#000000)都是合法范围。
7、为什么现有第三方Launcher即使在Android13上,Widget的取色也会无效,而 Pixel Launcher可以。
第三方 Launcher 代码中虽然已经有调用AppWidgetHostView.setColorResources,但是因为其提取颜色的LocalColorExtractor是空实现,所以没有实际效果。而Pixel Launcher 自己写了一个LocalWallpaperColorsExtractor去继承LocalColorExtractor。详见以下代码:
/packages/apps/Launcher3/src/com/android/launcher3/widget/LocalColorExtractor.java
...
public static LocalColorExtractor newInstance(Context context) {
return Overrides.getObject(WallpaperColors.class, context.getApplicationContext(),
R.string.local_colors_extraction_class);
}
...
local_colors_extraction_class 该值定义在PixelLauncher中
<string name="local_colors_extraction_class">com.google.android.apps.nexuslauncher.widget.LocalWallpaperColorsExtractor</string>
8、是否可以固定设置一套或者几套颜色,去overlay 壁纸的取色?
可以,Android 13已经在secure表中预定义了theme_customization_overlay_packages字段,在其中配置了是否要根据主屏幕变色还是预置颜色,然后进行资源Overlay的。
Android 原生设置入口
theme_customization_overlay_packages字段含义
字段 | 含义 | 取值范围 |
---|---|---|
android.theme.customization.color_index | 选择颜色索引 | 0~n |
android.theme.customization.system_palette | 中立色 | color |
android.theme.customization.accent_color | 强调色 | color |
android.theme.customization.color_source | 采用跟随壁纸颜色还是基本颜色 | present \ home_wallpaper \ lock_wallpaper |
android.theme.customization.color_both" | 区分当前壁纸设置类型 | 1 锁屏和主屏幕, 0 只设置其中一项 |
我们可以直接使用或借鉴这套逻辑,预置几套色值,并设置为使用基本颜色。
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)