最近项目需求修改Launcher3的源码,需求是给图标加上蒙层。
阅读源码发现图标的缓存是在com/android.launcher3/graphics目录下的LauncherIcons.java类中修改。
在createIconBitmap()方法中
修改之前的代码:
public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
synchronized (sCanvas) {
final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
int width = iconBitmapSize;
int height = iconBitmapSize;
if (icon instanceof PaintDrawable) {
PaintDrawable painter = (PaintDrawable) icon;
painter.setIntrinsicWidth(width);
painter.setIntrinsicHeight(height);
} else if (icon instanceof BitmapDrawable) {
// Ensure the bitmap has a density.
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
}
}
int sourceWidth = icon.getIntrinsicWidth();
int sourceHeight = icon.getIntrinsicHeight();
if (sourceWidth > 0 && sourceHeight > 0) {
// Scale the icon proportionally to the icon dimensions
final float ratio = (float) sourceWidth / sourceHeight;
if (sourceWidth > sourceHeight) {
height = (int) (width / ratio);
} else if (sourceHeight > sourceWidth) {
width = (int) (height * ratio);
}
}
// no intrinsic size --> use default size
int textureWidth = iconBitmapSize;
int textureHeight = iconBitmapSize;
Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
Bitmap.Config.ARGB_8888);
final Canvas canvas = sCanvas;
canvas.setBitmap(bitmap);
final int left = (textureWidth - width) / 2;
final int top = (textureHeight - height) / 2;
sOldBounds.set(icon.getBounds());
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
int offset = Math.max((int) (ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
Math.min(left, top));
int size = Math.max(width, height);
icon.setBounds(offset, offset, size, size);
} else {
icon.setBounds(left, top, left + width, top + height);
}
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
icon.draw(canvas);
canvas.restore();
icon.setBounds(sOldBounds);
canvas.setBitmap(null);
return bitmap;
}
}
只需要在绘制完图标后再一个Bitmap就行了:
Bitmap bitmapStop = BitmapFactory.decodeResource(context.getResources(), R.mipmap.icon_stop);
Rect rect = new Rect(left, top, left + width, top + height);
canvas.drawBitmap(bitmapStop, null, rect, new Paint());
修改完之后代码:
public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
synchronized (sCanvas) {
final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
int width = iconBitmapSize;
int height = iconBitmapSize;
if (icon instanceof PaintDrawable) {
PaintDrawable painter = (PaintDrawable) icon;
painter.setIntrinsicWidth(width);
painter.setIntrinsicHeight(height);
} else if (icon instanceof BitmapDrawable) {
// Ensure the bitmap has a density.
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
}
}
int sourceWidth = icon.getIntrinsicWidth();
int sourceHeight = icon.getIntrinsicHeight();
if (sourceWidth > 0 && sourceHeight > 0) {
// Scale the icon proportionally to the icon dimensions
final float ratio = (float) sourceWidth / sourceHeight;
if (sourceWidth > sourceHeight) {
height = (int) (width / ratio);
} else if (sourceHeight > sourceWidth) {
width = (int) (height * ratio);
}
}
// no intrinsic size --> use default size
int textureWidth = iconBitmapSize;
int textureHeight = iconBitmapSize;
Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
Bitmap.Config.ARGB_8888);
final Canvas canvas = sCanvas;
canvas.setBitmap(bitmap);
final int left = (textureWidth - width) / 2;
final int top = (textureHeight - height) / 2;
sOldBounds.set(icon.getBounds());
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
int offset = Math.max((int) (ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
Math.min(left, top));
int size = Math.max(width, height);
icon.setBounds(offset, offset, size, size);
} else {
icon.setBounds(left, top, left + width, top + height);
}
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
icon.draw(canvas);
// TODO: 2021/7/27 添加背景
Bitmap bitmapStop = BitmapFactory.decodeResource(context.getResources(), R.mipmap.icon_stop);
Rect rect = new Rect(left, top, left + width, top + height);
canvas.drawBitmap(bitmapStop, null, rect, new Paint());
canvas.restore();
icon.setBounds(sOldBounds);
canvas.setBitmap(null);
return bitmap;
}
}
此处可以通过包名判断哪些应用图标需要添加蒙层。
而我们的需求是需要动态变换图标 通过网络请求获取到需要添加蒙层的应用包名,然后进行区分添加不同的蒙层。
在网络请求中调用LauncherModel的updateSessionDisplayInfo()方法传递的参数是需要修改的图标包名
mModel.updateSessionDisplayInfo("需要修改的包名");
public void updateSessionDisplayInfo(final String packageName) {
HashSet<String> packages = new HashSet<>();
packages.add(packageName);
enqueueModelUpdateTask(new CacheDataUpdatedTask(
CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
}
该方法会调用CacheDataUpdatedTask类的execute()方法
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
IconCache iconCache = app.getIconCache();
final ArrayList<AppInfo> updatedApps = new ArrayList<>();
ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
synchronized (dataModel) {
for (ItemInfo info : dataModel.itemsIdMap) {
if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
ShortcutInfo si = (ShortcutInfo) info;
ComponentName cn = si.getTargetComponent();
if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
&& isValidShortcut(si) && cn != null
&& mPackages.contains(cn.getPackageName())) {
iconCache.getTitleAndIcon(si, si.usingLowResIcon);
updatedShortcuts.add(si);
}
}
}
apps.updateIconsAndLabels(mPackages, mUser, updatedApps);
}
bindUpdatedShortcuts(updatedShortcuts, mUser);
if (!updatedApps.isEmpty()) {
scheduleCallbackTask(new CallbackTask() {
@Override
public void execute(Callbacks callbacks) {
callbacks.bindAppsAddedOrUpdated(updatedApps);
}
});
}
}
然后就会调用CacheDataUpdatedTask类的
apps.updateIconsAndLabels(mPackages, mUser, updatedApps);方法
在往后就会调用AllAppsList类的updateIconsAndLabels()方法
public void updateIconsAndLabels(HashSet<String> packages, UserHandle user,
ArrayList<AppInfo> outUpdates) {
for (AppInfo info : data) {
if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
mIconCache.updateTitleAndIcon(info);
outUpdates.add(info);
}
}
}
最终走到IconCache类的cacheLocked()方法中
protected CacheEntry cacheLocked(
@NonNull ComponentName componentName,
@NonNull Provider<LauncherActivityInfo> infoProvider,
UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
Preconditions.assertWorkerThread();
ComponentKey cacheKey = new ComponentKey(componentName, user);
CacheEntry entry = mCache.get(cacheKey);
if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
entry = new CacheEntry();
mCache.put(cacheKey, entry);
// Check the DB first.
LauncherActivityInfo info = null;
boolean providerFetchedOnce = false;
if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
info = infoProvider.get();
providerFetchedOnce = true;
if (info != null) {
List<DevGetallAppStatus.AppsBean> appsBeans1 = new ArrayList<>();
List<DevGetallAppStatus.AppsBean> appsBeans2 = new ArrayList<>();
if (Launcher.allAppStatus != null && Launcher.allAppStatus != null && Launcher.allAppStatus.size() > 0) {
for (int i = 0; i < Launcher.allAppStatus.size(); i++) {
if (Launcher.allAppStatus.get(i).enable.equals("0")) {
appsBeans1.add(Launcher.allAppStatus.get(i));
} else if (Launcher.allAppStatus.get(i).enable.equals("2")) {
appsBeans2.add(Launcher.allAppStatus.get(i));
}
}
}
entry.icon = LauncherIcons.createBadgedIconBitmap(
getFullResIcon(info), info.getUser(), mContext,
infoProvider.get().getApplicationInfo().targetSdkVersion);
for (int i = 0; i < appsBeans1.size(); i++) {
if (info.getComponentName().getPackageName().equals(appsBeans1.get(i).packageName)) {
entry.icon = LauncherIcons.createBadgedIconBitmapRed(
getFullResIcon(info), info.getUser(), mContext,
infoProvider.get().getApplicationInfo().targetSdkVersion);
}
}
for (int i = 0; i < appsBeans2.size(); i++) {
if (info.getComponentName().getPackageName().equals(appsBeans2.get(i).packageName)) {
entry.icon = LauncherIcons.createBadgedIconBitmapGreen(
getFullResIcon(info), info.getUser(), mContext,
infoProvider.get().getApplicationInfo().targetSdkVersion);
}
}
} else {
if (usePackageIcon) {
CacheEntry packageEntry = getEntryForPackageLocked(
componentName.getPackageName(), user, false);
if (packageEntry != null) {
if (DEBUG) Log.d(TAG, "using package default icon for " +
componentName.toShortString());
entry.icon = packageEntry.icon;
entry.title = packageEntry.title;
entry.contentDescription = packageEntry.contentDescription;
}
}
if (entry.icon == null) {
if (DEBUG) Log.d(TAG, "using default icon for " +
componentName.toShortString());
entry.icon = getDefaultIcon(user);
}
}
}
if (TextUtils.isEmpty(entry.title)) {
if (info == null && !providerFetchedOnce) {
info = infoProvider.get();
providerFetchedOnce = true;
}
if (info != null) {
entry.title = info.getLabel();
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
}
}
}
if (Launcher.changeAppsBeans.size() > 0) {
if (infoProvider.get() != null && infoProvider.get().getComponentName() != null) {
LauncherActivityInfo info = infoProvider.get();
List<DevGetallAppStatus.AppsBean> appsBeans1 = new ArrayList<>();
List<DevGetallAppStatus.AppsBean> appsBeans2 = new ArrayList<>();
List<DevGetallAppStatus.AppsBean> appsBeans3 = new ArrayList<>();
for (int i = 0; i < Launcher.changeAppsBeans.size(); i++) {
if (Launcher.changeAppsBeans.get(i).enable.equals("0")) {
appsBeans1.add(Launcher.changeAppsBeans.get(i));
} else if (Launcher.changeAppsBeans.get(i).enable.equals("2")) {
appsBeans2.add(Launcher.changeAppsBeans.get(i));
} else {
appsBeans3.add(Launcher.changeAppsBeans.get(i));
}
}
for (int i = 0; i < appsBeans1.size(); i++) {
if (info.getComponentName().getPackageName().equals(appsBeans1.get(i).packageName)) {
entry.icon = LauncherIcons.createBadgedIconBitmapRed(
getFullResIcon(info), info.getUser(), mContext,
infoProvider.get().getApplicationInfo().targetSdkVersion);
Log.i("lee", "执行完list1禁用修改" + info.getComponentName().getPackageName());
}
}
for (int i = 0; i < appsBeans2.size(); i++) {
if (info.getComponentName().getPackageName().equals(appsBeans2.get(i).packageName)) {
entry.icon = LauncherIcons.createBadgedIconBitmapGreen(
getFullResIcon(info), info.getUser(), mContext,
infoProvider.get().getApplicationInfo().targetSdkVersion);
Log.i("lee", "执行完list2限时修改" + info.getComponentName().getPackageName());
}
}
for (int i = 0; i < appsBeans3.size(); i++) {
if (info.getComponentName().getPackageName().equals(appsBeans3.get(i).packageName)) {
entry.icon = LauncherIcons.createBadgedIconBitmap(
getFullResIcon(info), info.getUser(), mContext,
infoProvider.get().getApplicationInfo().targetSdkVersion);
Log.i("lee", "执行完list3*修改" + info.getComponentName().getPackageName());
}
}
Launcher.changeAppsBeans.clear();
}
}
return entry;
}
主要逻辑在此处通过包名判断修改数据列表
LauncherActivityInfo info = infoProvider.get();
List<DevGetallAppStatus.AppsBean> appsBeans1 = new ArrayList<>();
List<DevGetallAppStatus.AppsBean> appsBeans2 = new ArrayList<>();
List<DevGetallAppStatus.AppsBean> appsBeans3 = new ArrayList<>();
for (int i = 0; i < Launcher.changeAppsBeans.size(); i++) {
if (Launcher.changeAppsBeans.get(i).enable.equals("0")) {
appsBeans1.add(Launcher.changeAppsBeans.get(i));
} else if (Launcher.changeAppsBeans.get(i).enable.equals("2")) {
appsBeans2.add(Launcher.changeAppsBeans.get(i));
} else {
appsBeans3.add(Launcher.changeAppsBeans.get(i));
}
}
然后复制两个LauncherIcons类中的createBadgedIconBitmap()方法 如果有3中蒙层就复制3个(比较笨和懒的方法)
就会调用以下方法
public static Bitmap createBadgedIconBitmap(
Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
IconNormalizer normalizer;
float scale = 1f;
if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
normalizer = IconNormalizer.getInstance(context);
if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
boolean[] outShape = new boolean[1];
AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
dr.setBounds(0, 0, 1, 1);
scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape);
if (FeatureFlags.LEGACY_ICON_TREATMENT &&
!outShape[0]) {
Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
if (wrappedIcon != icon) {
icon = wrappedIcon;
scale = normalizer.getScale(icon, null, null, null);
}
}
} else {
scale = normalizer.getScale(icon, null, null, null);
}
}
Bitmap bitmap = createIconBitmap(icon, context, scale);
if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
icon instanceof AdaptiveIconDrawable) {
bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
}
return badgeIconForUser(bitmap, user, context);
}
最终调用一开始说的方法:
public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
synchronized (sCanvas) {
final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
int width = iconBitmapSize;
int height = iconBitmapSize;
if (icon instanceof PaintDrawable) {
PaintDrawable painter = (PaintDrawable) icon;
painter.setIntrinsicWidth(width);
painter.setIntrinsicHeight(height);
} else if (icon instanceof BitmapDrawable) {
// Ensure the bitmap has a density.
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
}
}
int sourceWidth = icon.getIntrinsicWidth();
int sourceHeight = icon.getIntrinsicHeight();
if (sourceWidth > 0 && sourceHeight > 0) {
// Scale the icon proportionally to the icon dimensions
final float ratio = (float) sourceWidth / sourceHeight;
if (sourceWidth > sourceHeight) {
height = (int) (width / ratio);
} else if (sourceHeight > sourceWidth) {
width = (int) (height * ratio);
}
}
// no intrinsic size --> use default size
int textureWidth = iconBitmapSize;
int textureHeight = iconBitmapSize;
Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
Bitmap.Config.ARGB_8888);
final Canvas canvas = sCanvas;
canvas.setBitmap(bitmap);
final int left = (textureWidth - width) / 2;
final int top = (textureHeight - height) / 2;
sOldBounds.set(icon.getBounds());
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
int offset = Math.max((int) (ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
Math.min(left, top));
int size = Math.max(width, height);
icon.setBounds(offset, offset, size, size);
} else {
icon.setBounds(left, top, left + width, top + height);
}
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
icon.draw(canvas);
canvas.restore();
icon.setBounds(sOldBounds);
canvas.setBitmap(null);
return bitmap;
}
}
这样就完成了对Launcher3图标的动态添加蒙层。
在提一句影藏某些包名的应用图标就只需要在:
IconCache的cacheLocked()方法中通过包名过滤是否需要展示某些应用图标。