在 Android13 Launcher3 中桌面图标添加 HiCar 和 ICCOA 角标需要对 Launcher3 源码进行定制开发。先展示效果:

以下是实现这一功能的步骤和代码示例:
一、准备工作
- 获取 Launcher3 源码 从 AOSP 获取 Android13 Launcher3 源码。
- 理解 Launcher3 架构 重点关注
BitmapInfo
、BaseIconFactory
、FastBitmapDrawable
和IconCache
类。
二、创建角标资源
- 在
res/drawable
目录下添加 HiCar 和 ICCOA 角标图片(如ic_hicar_badge.png
、ic_iccoa_badge.png
)。
三、修改 BitmapInfo
类添加角标标识
- 在
BitmapInfo.java
中添加角标状态字段 - 在
BitmapInfo.java
类中修改applyFlags
方法,向 icon 中添加 HiCar 或 ICCOA 角标资源
java
public class BitmapInfo {
static final int FLAG_WORK = 1 << 0;
static final int FLAG_INSTANT = 1 << 1;
// Add:HiCar & ICCOA badge flags
static final int FLAG_ICCOA = 1 << 2;
static final int FLAG_HICAR = 1 << 3;
@IntDef(flag = true, value = {
FLAG_WORK,
FLAG_INSTANT,
// Add: HiCar & ICCOA badge flags
FLAG_ICCOA,
FLAG_HICAR,
})
...
// Update applyFlags function
protected void applyFlags(Context context, FastBitmapDrawable drawable,
@DrawableCreationFlags int creationFlags) {
drawable.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
if ((creationFlags & FLAG_NO_BADGE) == 0) {
if (badgeInfo != null) {
drawable.setBadge(badgeInfo.newIcon(context, creationFlags));
} else if ((flags & FLAG_INSTANT) != 0) {
drawable.setBadge(AppCompatResources.getDrawable(context, R.drawable.ic_instant_app_badge));
} else if ((flags & FLAG_WORK) != 0) {
drawable.setBadge(AppCompatResources.getDrawable(context, R.drawable.ic_work_app_badge));
}
// Add: HiCar & ICCOA badge resources
if ((flags & FLAG_ICCOA) != 0) {
Log.d(TAG, "applyFlags: set the iccoa badge");
drawable.setBadge(AppCompatResources.getDrawable(context, R.drawable.ic_iccoa_badge));
}
if ((flags & FLAG_HICAR) != 0) {
Log.d(TAG, "applyFlags: set the hicar badge");
drawable.setBadge(AppCompatResources.getDrawable(context, R.drawable.ic_hicar_badge));
}
}
}
四、修改 BaseIconFactory
类处理角标逻辑
- 在
BaseIconFactory.java
中修改常量ICON_BADGE_SCALE
,控制 badge 的大小 - 在
BaseIconFactory.java
内部类IconOptions
中添加isIccoaApp
和isHiCarApp
字段,暴露设置 HiCar 或 ICCOA 标识方法 - 在
BaseIconFactory.java
中修改方法getBitmapFlagOp
,添加 HiCar 和 ICCOA flag
中方法判断应用是否需要角标:
java
public class BaseIconFactory implements AutoCloseable {
// Update the badge scale size to resize the badge
private static final float ICON_BADGE_SCALE = 0.444f;
...
/**
* Returns the correct badge size given an icon size
*/
public static int getBadgeSizeForIconSize(int iconSize) {
return (int) (ICON_BADGE_SCALE * iconSize);
}
// Update getBitmapFlagOp function
public FlagOp getBitmapFlagOp(@Nullable IconOptions options) {
FlagOp op = FlagOp.NO_OP;
if (options != null) {
if (options.mIsInstantApp) {
op = op.addFlag(FLAG_INSTANT);
}
if (options.mIsHiCarApp) {
op = op.addFlag(FLAG_HICAR);
}
if (options.mIsIccoaApp) {
op = op.addFlag(FLAG_ICCOA);
}
}
}
public static class IconOptions {
boolean mShrinkNonAdaptiveIcons = true;
boolean mIsInstantApp;
UserHandle mUserHandle;
// Add is hicar & is iccoa app vars
boolean mIsIccoaApp;
boolean mIsHiCarApp;
...
public IconOptions setIccoaApp(boolean iccoaApp) {
mIsIccoaApp = iccoaApp;
return this;
}
public IconOptions setHiCarApp(boolean hicarApp) {
mIsHiCarApp = hicarApp;
return this;
}
}
}
五、FastBitmapDrawable
绘制角标(按需修改)
FastBitmapDrawable.java
中已有角标绘制逻辑,可按需修改:
java
public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
// Set badge, we've set in the BitmapInfo class before
public void setBadge(Drawable badge) {
if (mBadge != null) {
mBadge.setCallback(null);
}
mBadge = badge;
if (mBadge != null) {
mBadge.setCallback(this);
}
updateBadgeBounds(getBounds());
updateFilter();
}
private void updateBadgeBounds(Rect bounds) {
if (mBadge != null) {
setBadgeBounds(mBadge, bounds);
}
}
/**
* Sets the bounds for the badge drawable based on the main icon bounds
*/
public static void setBadgeBounds(Drawable badge, Rect iconBounds) {
// The size controlled by the ICON_BADGE_SCALE constant field in the
// BaseIconFactory class
int size = getBadgeSizeForIconSize(iconBounds.width());
badge.setBounds(iconBounds.right - size, iconBounds.bottom - size,
iconBounds.right, iconBounds.bottom);
}
@Override
public final void draw(Canvas canvas) {
if (mScale != 1f) {
int count = canvas.save();
Rect bounds = getBounds();
canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
drawInternal(canvas, bounds);
// Draw the badge logic
if (mBadge != null) {
mBadge.draw(canvas);
}
canvas.restoreToCount(count);
} else {
drawInternal(canvas, getBounds());
// Draw the badge logic
if (mBadge != null) {
mBadge.draw(canvas);
}
}
}
六、使用方式
在对桌面图标替换 Icon 资源时,我们可以同时添加 IconOptions 属性:
kotlin
fun swapIcon(context: Context, info: ItemInfoWithIcon, source: Bitmap) {
val launcherIcons = LauncherIcons.obtain(context)
val targetDrawable = source.toDrawable(context.resources)
// Set icon option as iccoa app, show the iccoa badge
val iconOptions = IconOptions().setIccoaApp(true) // or .setHiCarApp(true)
val bitmapInfo = launcherIcons.createBadgedIconBitmap(targetDrawable, iconOptions)
bitmapInfo?.let {
info.bitmap = bitmapInfo
}
}
七、修改完的样式
