Launcher3图标相关的修改记录【Android13版本】

概要

本文记录Launcher的图标修改涉及到的一些技术点,如有错误还请指正,欢迎大家一起探讨。

例如:

如何替换个别图标?替换后图标被放大怎么处理?
如何更换图标圆角?
如何去掉”非自适应“图标默认的白色背景?
图标增加遥控选中状态

替换图标(前)

在这里插入图片描述
替换图标后:
在这里插入图片描述

一、更换图标圆角样式

修改文件:frameworks/base/core/res/res/values/config.xml

<string name="config_icon_mask" translatable="false">"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"</string>

找到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.java
public 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应用制作自适应图标

修改的类:
\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;
    }

四、图标增加遥控选中状态

修改的类:
\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

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true">
    <shape>
        <solid android:color="#F44336"/>
    </shape>
</item>
<item android:state_hovered="true">
    <shape>
        <solid android:color="#F44336"/>
    </shape>
</item>
<item android:state_focused="true">
	<shape>
		<solid android:color="#F44336"/>
	</shape>
</item>
<item android:state_selected="true">
    <shape>
        <solid android:color="#F44336"/>
    </shape>
</item>
<item android:state_selected="false">
    <shape>
        <solid android:color="#00000000"/>
    </shape>
</item>
<item android:state_pressed="false">
    <shape>
        <solid android:color="#00000000"/>
    </shape>
</item>
<item android:state_focused="false">
    <shape>
        <solid android:color="#00000000"/>
    </shape>
</item>
</selector>
  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值