行业知识
Launcher3图标相关的修改记录【Android13版本】
文章目录
- 概要
- 一、更换图标圆角样式
- 二、替换图标
- 2.1 图标被放大
- 2.2 图标拖拽的时候被打回原型
- 三、去掉”非自适应“图标默认的白色背景
- 解决方案一:直接去掉白色背景
- 方案二:放大前景图标,达到正好盖住白色背景
- 四、图标增加遥控选中状态
概要
本文记录Launcher的图标修改涉及到的一些技术点,如有错误还请指正,欢迎大家一起探讨。
例如:
如何替换个别图标?替换后图标被放大怎么处理?
如何更换图标圆角?
如何去掉”非自适应“图标默认的白色背景?
图标增加遥控选中状态
替换图标(前)
替换图标后:
一、更换图标圆角样式
修改文件:frameworks/base/core/res/res/values/config.xml
"M50,0 L85,0 C90.42,0 100,4.58 100,15 L100,85 C100,90.42 96.42,100 85,100 L15,100 C10.58,100 0,96.42 0,85 L0,15 C0,10.42 4.42,0 15,0 L50,0 Z"
找到config_icon_mask属性,修改其中的vector资源(M50,0 L85 …这一串)就能改成任意形状。具体大家可以在Android Studio中制作vector矢量图,或者网络搜一搜。
二、替换图标
修改的类:
\packages\apps\Launcher3\src\com\android\launcher3\BubbleTextView.java
BubbleTextView就是图标类,applyIconAndLabel方法中直接替换要更换图标。具体怎么调用到这里的直接在Android Studio中点击相关的方法就能找的调用链,这里就不展开了。
@UiThread protected void applyIconAndLabel(ItemInfoWithIcon info) { boolean useTheme = mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER || mDisplay == DISPLAY_TASKBAR; int flags = useTheme ? FLAG_THEMED : 0; if (mHideBadge) { flags |= FLAG_NO_BADGE; } // 改变图标的样式 begin BitmapInfo bitmapInfo = convertBitmapInfoSpecial(info.getTargetComponent().getPackageName()); if (bitmapInfo != null){ info.bitmap = bitmapInfo; } // 改变图标的样式 end FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags); mDotParams.appColor = iconDrawable.getIconColor(); mDotParams.dotColor = getContext().getResources() .getColor(android.R.color.system_accent3_200, getContext().getTheme()); setIcon(iconDrawable); applyLabel(info); }/*** 根据包名替换app图标*/public BitmapInfo convertBitmapInfoSpecial(String pckName){ if (pckName == null || "".equals(pckName)) return null; if (customIconMap.containsKey(pckName)){ LauncherIcons li = LauncherIcons.obtain(mContext); Drawable d = ContextCompat.getDrawable(mContext, customIconMap.get(pckName)); Bitmap bitmap = drawableToBitmap(d); return li.createIconBitmap(bitmap); } return null;}// 静态代码块中初始话一个哈希表。最好写个公共类,创建一个静态的customIconMap 哈希表,这样各个想用的类都可以调用static{// 构造一个包名:资源名的哈希表customIconMap = new HashMap(); customIconMap.put("com.android.settings", com.android.launcher3.icons.R.drawable.ic_launcher_setting); // 比如我们要替换设置应用的图标,在这里put进去其对应的图标资源}经过这样修改后,就能替换掉launcher中的图标
2.1 图标被放大
上面的简单替换可能导致图标被放大了,这里再介绍如何解决这个问题。
核心就是convertBitmapInfoSpecial()方法中,LauncherIcons类是继承BaseIconFactory,因此li.createIconBitmap(bitmap); 其实用的是BaseIconFactory类中的createIconBitmap()
来看BaseIconFactory#createIconBitmap(),代码如下
// BaseIconFactory.javapublic BitmapInfo createIconBitmap(Bitmap icon) { if (this.mIconBitmapSize != icon.getWidth() || this.mIconBitmapSize != icon.getHeight()) { icon = this.createIconBitmap(new BitmapDrawable(this.mContext.getResources(), icon), 1.0F); // 这里的1.0f太大了导致的 } return BitmapInfo.of(icon, this.mColorExtractor.findDominantColorByHue(icon));}因此我们在LauncherIcons中重写该方法:
修改的类:
\packages\apps\Launcher3\src\com\android\launcher3\icons\LauncherIcons.java
// LauncherIcons.java@Override public BitmapInfo createIconBitmap(Bitmap icon) { if (this.mIconBitmapSize != icon.getWidth() || this.mIconBitmapSize != icon.getHeight()) { icon = this.createIconBitmap(new BitmapDrawable(this.mContext.getResources(), icon), 0.8F); // 缩小点,否则换了图标之后会被放大 } return BitmapInfo.of(icon, this.mColorExtractor.findDominantColorByHue(icon)); }经过重写LauncherIcons#createIconBitmap()后,就能解决替换后的图标被放大问题。当然这个不是唯一的方案,可能还有其他的方法。
2.2 图标拖拽的时候被打回原型
关于拖拽的简单介绍,大家可以看下这篇文章:Launcher3拖拽分析(一)——Android13版本
其中拖拽的时候,会创建一个DragView。
DragView:BubbTextView的平替(他们携带的信息是一样的),因为BubbTextView的父布局是ShortcutAndWidgetContainer,如果拖拽到另一个ShortcutAndWidgetContainer是不允许的(跨布局了)。所以创造了一个DragView来代替BubbTextView
因此,在上面我们只是单纯的改了BubbleTextView中的图标内容,还要改下DragView的图标。
具体看DragView#setItemInfo() 方法:
public void setItemInfo(final ItemInfo info) { if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { return; } // Load the adaptive icon on a background thread and add the view in ui thread. MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> { Object[] outObj = new Object[1]; int w = mWidth; int h = mHeight; Drawable dr = Utilities.getFullDrawable(mActivity, info, w, h, true /* shouldThemeIcon */, outObj); // 关键就是这个 ......关键就是看 Drawable dr = Utilities.getFullDrawable()这个方法调用了啥,根据调用链依次追踪,最后发现这个
Utilities#loadFullDrawableWithoutTheme()
修改的类:
\packages\apps\Launcher3\src\com\android\launcher3\Utilities.java
private static Drawable loadFullDrawableWithoutTheme(Context context, ItemInfo info, int width, int height, Object[] outObj) { ActivityContext activity = ActivityContext.lookupContext(context); LauncherAppState appState = LauncherAppState.getInstance(context);// 如果是应用图标。走这里if代码块 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class) .resolveActivity(info.getIntent(), info.user); outObj[0] = activityInfo; return activityInfo == null ? null : LauncherAppState.getInstance(context) .getIconProvider().getIcon( activityInfo, activity.getDeviceProfile().inv.fillResIconDpi); }......}因此,我们还要根据特定的包名,重新返回一个新的Drawable对象,上修改后的代码
private static Drawable loadFullDrawableWithoutTheme(Context context, ItemInfo info, int width, int height, Object[] outObj) { ActivityContext activity = ActivityContext.lookupContext(context); LauncherAppState appState = LauncherAppState.getInstance(context); if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class) .resolveActivity(info.getIntent(), info.user); outObj[0] = activityInfo; Drawable drawableSpecial = getDrawableSpecial(info.getTargetComponent().getPackageName()); if (drawableSpecial == null){ return activityInfo == null ? null : LauncherAppState.getInstance(context) .getIconProvider().getIcon( activityInfo, activity.getDeviceProfile().inv.fillResIconDpi); }else{ return drawableSpecial; } } ......}public Drawable getDrawableSpecial(String pckName){ if (pckName == null || "".equals(pckName)) return null; if (customIconMap.containsKey(pckName)){ // customIconMap 最好弄成个静态的放在公共的类中,供大家调用 return ContextCompat.getDrawable(mContext, customIconMap.get(pckName)); } return null;}经过上面的修改后,就能解决拖拽的时候,被替换的图标又打回原型的问题。
三、去掉”非自适应“图标默认的白色背景
自适应图标介绍与制作,参看这篇文章:Android应用制作自适应图标
如果第三方app开发者,并未适配自适应icon图标,则安装后系统会默认添加一个白色背景。
例如:
解决方案一:直接去掉白色背景
修改的类:
\frameworks\libs\systemui\iconloaderlib\src\com\android\launcher3\icons\BaseIconFactory.java注意这个类是Framework中的源码。
主要就是normalizeAndWrapToAdaptiveIcon方法中,有对是否自适应图标的判断。如果不是自适应图标就要添加背景,同时修改scale;否则直接取出图标其scale来用。 因此直接屏蔽if、else判断,直接取图标本身的scale即可
protected Drawable normalizeAndWrapToAdaptiveIcon(@Nullable Drawable icon, final boolean shrinkNonAdaptiveIcons, @Nullable final RectF outIconBounds, @NonNull final float[] outScale) { if (icon == null) { return null; } float scale = 1f; // 1、原本是if.else.结构 把其注释掉 /* begin 注释 */ if (shrinkNonAdaptiveIcons && !(icon instanceof AdaptiveIconDrawable)) { if (mWrapperIcon == null) { mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper) .mutate(); } AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon; dr.setBounds(0, 0, 1, 1); boolean[] outShape = new boolean[1]; scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape); if (!outShape[0]) { FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground()); fsd.setDrawable(icon); fsd.setScale(scale); icon = dr; scale = getNormalizer().getScale(icon, outIconBounds, null, null); ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor); } } else { scale = getNormalizer().getScale(icon, outIconBounds, null, null); } /* end 注释 */ // 2、打开这个注释,就能去掉图像的白边 // scale = getNormalizer().getScale(icon, outIconBounds, null, null); outScale[0] = scale; return icon; }此方案会误伤不规则图标:即有些icon本身不是方形的,而是不规则图形,不规则图标本来就要有个白色背景。
方案二:放大前景图标,达到正好盖住白色背景
既然对于不规则图标,白色背景有存在的必要,且规则图标又不想要白色背景,只有放大前景图标的大小了。
效果如下:
修改的类:
\frameworks\libs\systemui\iconloaderlib\src\com\android\launcher3\icons\FixedScaleDrawable.java
public class FixedScaleDrawable extends DrawableWrapper { // TODO b/33553066 use the constant defined in MaskableIconDrawable // private static final float LEGACY_ICON_SCALE = .7f * .6667f; private static final float LEGACY_ICON_SCALE = 0.86f; // 这个静态变量就是控制前景图标缩放比例的,我这里设置成0.86正好能让前景图标盖住白色背景 private float mScaleX, mScaleY; public FixedScaleDrawable() { super(new ColorDrawable()); mScaleX = LEGACY_ICON_SCALE; mScaleY = LEGACY_ICON_SCALE; }......}四、图标增加遥控选中状态
修改的类:
\packages\apps\Launcher3\src\com\android\launcher3\BubbleTextView.java
BubbleTextView就是图标的载体(包含一个icon图片、对应的名字)。增加选中背景就是新建一个背景资源文件,同时在BubbleTextView的构造方法中setBackgroundResource()中设置这个资源文件即可
public BubbleTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle);......setBackgroundResource(R.drawable.select_background); // 增加这一句}新建资源文件 \packages\apps\Launcher3\res\drawable\select_background.xml
下一篇:小米怎么双开游戏服务器?