当前位置: 首页 > news >正文

Android12 launcher3修改App图标白边问题

Android12 launcher3修改App图标白边问题

1.前言:

今天在Android12 Rom定制客制化系统应用时发现改变系统App图标的形状会出现一个问题,那就是图标被缩小了,没有显示完整,有一个白边,这在普通的App开发很少遇到,在Android系统Rom定制时才会出现此问题,记录一下修改过程.

2.修改前的截图如下:

2.1 圆角图标:

可以看到App的图标没有铺满,中间的图标有的是方形,有的是圆角,而且出现一个白边,显示不正常

2.2 圆形图标:

发现切换圆形图标后问题也有问题,更难看,而且有个图标还被放大了,白边很突兀,明细不正常.

3.修改BaseIconFactory:

核心修改方法如下:

    /*** Switches badging to left/right*/public void setBadgeOnLeft(boolean badgeOnLeft) {mBadgeOnLeft = badgeOnLeft;}/*** Sets the background color used for wrapped adaptive icon*/public void setWrapperBackgroundColor(int color) {mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;}/*** Disables the dominant color extraction for all icons loaded.*/public void disableColorExtraction() {mDisableColorExtractor = true;}private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {if (icon == null) {return null;}float scale = 1f;
/* if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {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 (!(icon instanceof AdaptiveIconDrawable) && !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);} */scale =getNormalizer().getScale(icon, outIconBounds, null, null);outScale[0] = scale;return icon;}
package com.android.launcher3.icons;import static android.graphics.Paint.DITHER_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;import androidx.annotation.NonNull;import com.android.launcher3.icons.BitmapInfo.Extender;/*** This class will be moved to androidx library. There shouldn't be any dependency outside* this package.*/
public class BaseIconFactory implements AutoCloseable {private static final String TAG = "BaseIconFactory";private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;private static final float ICON_BADGE_SCALE = 0.444f;private final Rect mOldBounds = new Rect();protected final Context mContext;private final Canvas mCanvas;private final PackageManager mPm;private final ColorExtractor mColorExtractor;private boolean mDisableColorExtractor;private boolean mBadgeOnLeft = false;protected final int mFillResIconDpi;protected final int mIconBitmapSize;private IconNormalizer mNormalizer;private ShadowGenerator mShadowGenerator;private final boolean mShapeDetection;private Drawable mWrapperIcon;private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;private Bitmap mUserBadgeBitmap;private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);private static final float PLACEHOLDER_TEXT_SIZE = 20f;private static int PLACEHOLDER_BACKGROUND_COLOR = Color.rgb(240, 240, 240);protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,boolean shapeDetection) {mContext = context.getApplicationContext();mShapeDetection = shapeDetection;mFillResIconDpi = fillResIconDpi;mIconBitmapSize = iconBitmapSize;mPm = mContext.getPackageManager();mColorExtractor = new ColorExtractor();mCanvas = new Canvas();mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));mTextPaint.setTextAlign(Paint.Align.CENTER);mTextPaint.setColor(PLACEHOLDER_BACKGROUND_COLOR);mTextPaint.setTextSize(context.getResources().getDisplayMetrics().density *PLACEHOLDER_TEXT_SIZE);clear();}public BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {this(context, fillResIconDpi, iconBitmapSize, false);}protected void clear() {mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;mDisableColorExtractor = false;mBadgeOnLeft = false;}public ShadowGenerator getShadowGenerator() {if (mShadowGenerator == null) {mShadowGenerator = new ShadowGenerator(mIconBitmapSize);}return mShadowGenerator;}public IconNormalizer getNormalizer() {if (mNormalizer == null) {mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);}return mNormalizer;}@SuppressWarnings("deprecation")public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {try {Resources resources = mPm.getResourcesForApplication(iconRes.packageName);if (resources != null) {final int id = resources.getIdentifier(iconRes.resourceName, null, null);// do not stamp old legacy shortcuts as the app may have already forgotten about itreturn createBadgedIconBitmap(resources.getDrawableForDensity(id, mFillResIconDpi),Process.myUserHandle() /* only available on primary user */,false /* do not apply legacy treatment */);}} catch (Exception e) {// Icon not found.}return null;}/*** Create a placeholder icon using the passed in text.** @param placeholder used for foreground element in the icon bitmap* @param color used for the foreground text color* @return*/public BitmapInfo createIconBitmap(String placeholder, int color) {if (!ATLEAST_OREO) return null;Bitmap placeholderBitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,Bitmap.Config.ARGB_8888);mTextPaint.setColor(color);Canvas canvas = new Canvas(placeholderBitmap);canvas.drawText(placeholder, mIconBitmapSize / 2, mIconBitmapSize * 5 / 8, mTextPaint);AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(new ColorDrawable(PLACEHOLDER_BACKGROUND_COLOR),new BitmapDrawable(mContext.getResources(), placeholderBitmap));Bitmap icon = createIconBitmap(drawable, 1f);return BitmapInfo.of(icon, extractColor(icon));}public BitmapInfo createIconBitmap(Bitmap icon) {if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) {icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);}return BitmapInfo.of(icon, extractColor(icon));}/*** Creates an icon from the bitmap cropped to the current device icon shape*/public BitmapInfo createShapedIconBitmap(Bitmap icon, UserHandle user) {Drawable d = new FixedSizeBitmapDrawable(icon);if (ATLEAST_OREO) {float inset = AdaptiveIconDrawable.getExtraInsetFraction();inset = inset / (1 + 2 * inset);d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),new InsetDrawable(d, inset, inset, inset, inset));}return createBadgedIconBitmap(d, user, true);}public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,boolean shrinkNonAdaptiveIcons) {return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);}public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,int iconAppTargetSdk) {return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);}public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,int iconAppTargetSdk, boolean isInstantApp) {return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);}public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,int iconAppTargetSdk, boolean isInstantApp, float[] scale) {boolean shrinkNonAdaptiveIcons = ATLEAST_P ||(ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale);}public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {boolean shrinkNonAdaptiveIcons = ATLEAST_P ||(ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);return  createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons);}/*** Creates bitmap using the source drawable and various parameters.* The bitmap is visually normalized with other icons and has enough spacing to add shadow.** @param icon                      source of the icon* @param user                      info can be used for a badge* @param shrinkNonAdaptiveIcons    {@code true} if non adaptive icons should be treated* @param isInstantApp              info can be used for a badge* @param scale                     returns the scale result from normalization* @return a bitmap suitable for disaplaying as an icon at various system UIs.*/public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user,boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {if (scale == null) {scale = new float[1];}icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);Bitmap bitmap = createIconBitmap(icon, scale[0]);if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {mCanvas.setBitmap(bitmap);getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);mCanvas.setBitmap(null);}if (isInstantApp) {badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));}if (user != null) {BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);Drawable badged = mPm.getUserBadgedIcon(drawable, user);if (badged instanceof BitmapDrawable) {bitmap = ((BitmapDrawable) badged).getBitmap();} else {bitmap = createIconBitmap(badged, 1f);}}int color = extractColor(bitmap);return icon instanceof BitmapInfo.Extender? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this, scale[0], user): BitmapInfo.of(bitmap, color);}public Bitmap getUserBadgeBitmap(UserHandle user) {if (mUserBadgeBitmap == null) {Bitmap bitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize, Bitmap.Config.ARGB_8888);Drawable badgedDrawable = mPm.getUserBadgedIcon(new FixedSizeBitmapDrawable(bitmap), user);if (badgedDrawable instanceof BitmapDrawable) {mUserBadgeBitmap = ((BitmapDrawable) badgedDrawable).getBitmap();} else {badgedDrawable.setBounds(0, 0, mIconBitmapSize, mIconBitmapSize);mUserBadgeBitmap = BitmapRenderer.createSoftwareBitmap(mIconBitmapSize, mIconBitmapSize, badgedDrawable::draw);}}return mUserBadgeBitmap;}public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {RectF iconBounds = new RectF();float[] scale = new float[1];icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);return createIconBitmap(icon,Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));}/*** Switches badging to left/right*/public void setBadgeOnLeft(boolean badgeOnLeft) {mBadgeOnLeft = badgeOnLeft;}/*** Sets the background color used for wrapped adaptive icon*/public void setWrapperBackgroundColor(int color) {mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;}/*** Disables the dominant color extraction for all icons loaded.*/public void disableColorExtraction() {mDisableColorExtractor = true;}private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {if (icon == null) {return null;}float scale = 1f;
/* if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {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 (!(icon instanceof AdaptiveIconDrawable) && !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);} */scale =getNormalizer().getScale(icon, outIconBounds, null, null);outScale[0] = scale;return icon;}/*** Adds the {@param badge} on top of {@param target} using the badge dimensions.*/public void badgeWithDrawable(Bitmap target, Drawable badge) {mCanvas.setBitmap(target);badgeWithDrawable(mCanvas, badge);mCanvas.setBitmap(null);}/*** Adds the {@param badge} on top of {@param target} using the badge dimensions.*/public void badgeWithDrawable(Canvas target, Drawable badge) {int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);if (mBadgeOnLeft) {badge.setBounds(0, mIconBitmapSize - badgeSize, badgeSize, mIconBitmapSize);} else {badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,mIconBitmapSize, mIconBitmapSize);}badge.draw(target);}private Bitmap createIconBitmap(Drawable icon, float scale) {return createIconBitmap(icon, scale, mIconBitmapSize);}/*** @param icon drawable that should be flattened to a bitmap* @param scale the scale to apply before drawing {@param icon} on the canvas*/public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);if (icon == null) {return bitmap;}mCanvas.setBitmap(bitmap);mOldBounds.set(icon.getBounds());if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),Math.round(size * (1 - scale) / 2 ));icon.setBounds(offset, offset, size - offset, size - offset);if (icon instanceof BitmapInfo.Extender) {((Extender) icon).drawForPersistence(mCanvas);} else {icon.draw(mCanvas);}} else {if (icon instanceof BitmapDrawable) {BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;Bitmap b = bitmapDrawable.getBitmap();if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());}}int width = size;int height = size;int intrinsicWidth = icon.getIntrinsicWidth();int intrinsicHeight = icon.getIntrinsicHeight();if (intrinsicWidth > 0 && intrinsicHeight > 0) {// Scale the icon proportionally to the icon dimensionsfinal float ratio = (float) intrinsicWidth / intrinsicHeight;if (intrinsicWidth > intrinsicHeight) {height = (int) (width / ratio);} else if (intrinsicHeight > intrinsicWidth) {width = (int) (height * ratio);}}final int left = (size - width) / 2;final int top = (size - height) / 2;icon.setBounds(left, top, left + width, top + height);mCanvas.save();mCanvas.scale(scale, scale, size / 2, size / 2);icon.draw(mCanvas);mCanvas.restore();}icon.setBounds(mOldBounds);mCanvas.setBitmap(null);return bitmap;}@Overridepublic void close() {clear();}public BitmapInfo makeDefaultIcon(UserHandle user) {return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi),user, Build.VERSION.SDK_INT);}public static Drawable getFullResDefaultActivityIcon(int iconDpi) {return Resources.getSystem().getDrawableForDensity(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,iconDpi);}/*** Badges the provided source with the badge info*/public BitmapInfo badgeBitmap(Bitmap source, BitmapInfo badgeInfo) {Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {getShadowGenerator().recreateIcon(source, c);badgeWithDrawable(c, new FixedSizeBitmapDrawable(badgeInfo.icon));});return BitmapInfo.of(icon, badgeInfo.color);}private int extractColor(Bitmap bitmap) {return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap);}/*** Returns the correct badge size given an icon size*/public static int getBadgeSizeForIconSize(int iconSize) {return (int) (ICON_BADGE_SCALE * iconSize);}/*** An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.* This allows the badging to be done based on the action bitmap size rather than* the scaled bitmap size.*/private static class FixedSizeBitmapDrawable extends BitmapDrawable {public FixedSizeBitmapDrawable(Bitmap bitmap) {super(null, bitmap);}@Overridepublic int getIntrinsicHeight() {return getBitmap().getWidth();}@Overridepublic int getIntrinsicWidth() {return getBitmap().getWidth();}}
}

4.修改FixedScaleDrawable :

核心修改如下:

    private static final float LEGACY_ICON_SCALE = 1.0f;
package com.android.launcher3.icons;import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.Canvas;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.DrawableWrapper;
import android.util.AttributeSet;import org.xmlpull.v1.XmlPullParser;/*** Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.*/
public class FixedScaleDrawable extends DrawableWrapper {// TODO b/33553066 use the constant defined in MaskableIconDrawableprivate static final float LEGACY_ICON_SCALE = 1.0f;private float mScaleX, mScaleY;public FixedScaleDrawable() {super(new ColorDrawable());mScaleX = LEGACY_ICON_SCALE;mScaleY = LEGACY_ICON_SCALE;}@Overridepublic void draw(Canvas canvas) {int saveCount = canvas.save();canvas.scale(mScaleX, mScaleY,getBounds().exactCenterX(), getBounds().exactCenterY());super.draw(canvas);canvas.restoreToCount(saveCount);}@Overridepublic void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }@Overridepublic void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }public void setScale(float scale) {float h = getIntrinsicHeight();float w = getIntrinsicWidth();mScaleX = scale * LEGACY_ICON_SCALE;mScaleY = scale * LEGACY_ICON_SCALE;if (h > w && w > 0) {mScaleX *= w / h;} else if (w > h && h > 0) {mScaleY *= h / w;}}
}

5.修改IconNormalizer:

核心修改方法如下:

    private static float getScale(float hullArea, float boundingArea, float fullArea) {float hullByRect = hullArea / boundingArea;float scaleRequired;if (hullByRect < CIRCLE_AREA_BY_RECT) {scaleRequired = MAX_CIRCLE_AREA_FACTOR;} else {scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);}float areaScale = hullArea / fullArea;// Use sqrt of the final ratio as the images is scaled across both width and height.float scale = (float) Math.sqrt(scaleRequired / areaScale);return scale < 1f ? scale : 1f;}
/** Copyright (C) 2015 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.launcher3.icons;import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.Log;import java.nio.ByteBuffer;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;public class IconNormalizer {private static final String TAG = "IconNormalizer";private static final boolean DEBUG = false;// Ratio of icon visible area to full icon size for a square shaped iconprivate static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;// Ratio of icon visible area to full icon size for a circular shaped iconprivate static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;// Slope used to calculate icon visible area to full icon size for any generic shaped icon.private static final float LINEAR_SCALE_SLOPE =(MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);private static final int MIN_VISIBLE_ALPHA = 40;// Shape detection related constantsprivate static final float BOUND_RATIO_MARGIN = .05f;private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;private static final float SCALE_NOT_INITIALIZED = 0;// Ratio of the diameter of an normalized circular icon to the actual icon size.public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f;private final int mMaxSize;private final Bitmap mBitmap;private final Canvas mCanvas;private final Paint mPaintMaskShape;private final Paint mPaintMaskShapeOutline;private final byte[] mPixels;private final RectF mAdaptiveIconBounds;private float mAdaptiveIconScale;private boolean mEnableShapeDetection;// for each y, stores the position of the leftmost x and the rightmost xprivate final float[] mLeftBorder;private final float[] mRightBorder;private final Rect mBounds;private final Path mShapePath;private final Matrix mMatrix;/** package private **/IconNormalizer(Context context, int iconBitmapSize, boolean shapeDetection) {// Use twice the icon size as maximum size to avoid scaling down twice.mMaxSize = iconBitmapSize * 2;mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);mCanvas = new Canvas(mBitmap);mPixels = new byte[mMaxSize * mMaxSize];mLeftBorder = new float[mMaxSize];mRightBorder = new float[mMaxSize];mBounds = new Rect();mAdaptiveIconBounds = new RectF();mPaintMaskShape = new Paint();mPaintMaskShape.setColor(Color.RED);mPaintMaskShape.setStyle(Paint.Style.FILL);mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));mPaintMaskShapeOutline = new Paint();mPaintMaskShapeOutline.setStrokeWidth(2 * context.getResources().getDisplayMetrics().density);mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);mPaintMaskShapeOutline.setColor(Color.BLACK);mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));mShapePath = new Path();mMatrix = new Matrix();mAdaptiveIconScale = SCALE_NOT_INITIALIZED;mEnableShapeDetection = shapeDetection;}private static float getScale(float hullArea, float boundingArea, float fullArea) {float hullByRect = hullArea / boundingArea;float scaleRequired;if (hullByRect < CIRCLE_AREA_BY_RECT) {scaleRequired = MAX_CIRCLE_AREA_FACTOR;} else {scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);}float areaScale = hullArea / fullArea;// Use sqrt of the final ratio as the images is scaled across both width and height.float scale = (float) Math.sqrt(scaleRequired / areaScale);return scale < 1f ? scale : 1f;}/*** @param d Should be AdaptiveIconDrawable* @param size Canvas size to use*/@TargetApi(Build.VERSION_CODES.O)public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) {Rect tmpBounds = new Rect(d.getBounds());d.setBounds(0, 0, size, size);Path path = ((AdaptiveIconDrawable) d).getIconMask();Region region = new Region();region.setPath(path, new Region(0, 0, size, size));Rect hullBounds = region.getBounds();int hullArea = GraphicsUtils.getArea(region);if (outBounds != null) {float sizeF = size;outBounds.set(hullBounds.left / sizeF,hullBounds.top / sizeF,1 - (hullBounds.right / sizeF),1 - (hullBounds.bottom / sizeF));}d.setBounds(tmpBounds);return getScale(hullArea, hullArea, size * size);}/*** Returns if the shape of the icon is same as the path.* For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.*/private boolean isShape(Path maskPath) {// Condition1:// If width and height of the path not close to a square, then the icon shape is// not same as the mask shape.float iconRatio = ((float) mBounds.width()) / mBounds.height();if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {if (DEBUG) {Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);}return false;}// Condition 2:// Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation// should generate transparent image, if the actual icon is equivalent to the shape.// Fit the shape within the icon's bounding boxmMatrix.reset();mMatrix.setScale(mBounds.width(), mBounds.height());mMatrix.postTranslate(mBounds.left, mBounds.top);maskPath.transform(mMatrix, mShapePath);// XOR operationmCanvas.drawPath(mShapePath, mPaintMaskShape);// DST_OUT operation around the mask path outlinemCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);// Check if the result is almost transparentreturn isTransparentBitmap();}/*** Used to determine if certain the bitmap is transparent.*/private boolean isTransparentBitmap() {ByteBuffer buffer = ByteBuffer.wrap(mPixels);buffer.rewind();mBitmap.copyPixelsToBuffer(buffer);int y = mBounds.top;// buffer positionint index = y * mMaxSize;// buffer shift after every row, width of buffer = mMaxSizeint rowSizeDiff = mMaxSize - mBounds.right;int sum = 0;for (; y < mBounds.bottom; y++) {index += mBounds.left;for (int x = mBounds.left; x < mBounds.right; x++) {if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {sum++;}index++;}index += rowSizeDiff;}float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;}/*** Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it* matches the design guidelines for a launcher icon.** We first calculate the convex hull of the visible portion of the icon.* This hull then compared with the bounding rectangle of the hull to find how closely it* resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an* ideal solution but it gives satisfactory result without affecting the performance.** This closeness is used to determine the ratio of hull area to the full icon size.* Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}** @param outBounds optional rect to receive the fraction distance from each edge.*/public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,@Nullable Path path, @Nullable boolean[] outMaskShape) {if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);}if (outBounds != null) {outBounds.set(mAdaptiveIconBounds);}return mAdaptiveIconScale;}int width = d.getIntrinsicWidth();int height = d.getIntrinsicHeight();if (width <= 0 || height <= 0) {width = width <= 0 || width > mMaxSize ? mMaxSize : width;height = height <= 0 || height > mMaxSize ? mMaxSize : height;} else if (width > mMaxSize || height > mMaxSize) {int max = Math.max(width, height);width = mMaxSize * width / max;height = mMaxSize * height / max;}mBitmap.eraseColor(Color.TRANSPARENT);d.setBounds(0, 0, width, height);d.draw(mCanvas);ByteBuffer buffer = ByteBuffer.wrap(mPixels);buffer.rewind();mBitmap.copyPixelsToBuffer(buffer);// Overall bounds of the visible icon.int topY = -1;int bottomY = -1;int leftX = mMaxSize + 1;int rightX = -1;// Create border by going through all pixels one row at a time and for each row find// the first and the last non-transparent pixel. Set those values to mLeftBorder and// mRightBorder and use -1 if there are no visible pixel in the row.// buffer positionint index = 0;// buffer shift after every row, width of buffer = mMaxSizeint rowSizeDiff = mMaxSize - width;// first and last position for any row.int firstX, lastX;for (int y = 0; y < height; y++) {firstX = lastX = -1;for (int x = 0; x < width; x++) {if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {if (firstX == -1) {firstX = x;}lastX = x;}index++;}index += rowSizeDiff;mLeftBorder[y] = firstX;mRightBorder[y] = lastX;// If there is at least one visible pixel, update the overall bounds.if (firstX != -1) {bottomY = y;if (topY == -1) {topY = y;}leftX = Math.min(leftX, firstX);rightX = Math.max(rightX, lastX);}}if (topY == -1 || rightX == -1) {// No valid pixels found. Do not scale.return 1;}convertToConvexArray(mLeftBorder, 1, topY, bottomY);convertToConvexArray(mRightBorder, -1, topY, bottomY);// Area of the convex hullfloat area = 0;for (int y = 0; y < height; y++) {if (mLeftBorder[y] <= -1) {continue;}area += mRightBorder[y] - mLeftBorder[y] + 1;}mBounds.left = leftX;mBounds.right = rightX;mBounds.top = topY;mBounds.bottom = bottomY;if (outBounds != null) {outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height,1 - ((float) mBounds.right) / width,1 - ((float) mBounds.bottom) / height);}if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) {outMaskShape[0] = isShape(path);}// Area of the rectangle required to fit the convex hullfloat rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);return getScale(area, rectArea, width * height);}/*** Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values* (except on either ends) with appropriate values.* @param xCoordinates map of x coordinate per y.* @param direction 1 for left border and -1 for right border.* @param topY the first Y position (inclusive) with a valid value.* @param bottomY the last Y position (inclusive) with a valid value.*/private static void convertToConvexArray(float[] xCoordinates, int direction, int topY, int bottomY) {int total = xCoordinates.length;// The tangent at each pixel.float[] angles = new float[total - 1];int first = topY; // First valid y coordinateint last = -1;    // Last valid y coordinate which didn't have a missing valuefloat lastAngle = Float.MAX_VALUE;for (int i = topY + 1; i <= bottomY; i++) {if (xCoordinates[i] <= -1) {continue;}int start;if (lastAngle == Float.MAX_VALUE) {start = first;} else {float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);start = last;// If this position creates a concave angle, keep moving up until we find a// position which creates a convex angle.if ((currentAngle - lastAngle) * direction < 0) {while (start > first) {start --;currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);if ((currentAngle - angles[start]) * direction >= 0) {break;}}}}// Reset from last checklastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);// Update all the points from start.for (int j = start; j < i; j++) {angles[j] = lastAngle;xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);}last = i;}}/*** @return The diameter of the normalized circle that fits inside of the square (size x size).*/public static int getNormalizedCircleSize(int size) {float area = size * size * MAX_CIRCLE_AREA_FACTOR;return (int) Math.round(Math.sqrt((4 * area) / Math.PI));}
}

6.修改后的效果如下:

可以看到图标都正常显示了,不管是圆角还是圆形,当然还有方角、8边形、水滴图标等等。

6.1 圆角图标

在这里插入图片描述

6.2 圆形图标:

在这里插入图片描述

7.总结:

在Android12 Launcher3中系统设置App桌面图标形状后会出现一个白边,导致显示很难看,这明细需要修改,解决方法就是在系统源码中修改缩放级别,默认不缩小图标,以上方法需要编译源码后重新打包验证.

  • 修改BaseIconFactory,
  • 修改FixedScaleDrawable
  • 修改IconNormalizer
  • 当然这只是系统级修改,如果想要App本来的图标就适配Android12启动图标
  • 普通App不会有此问题,App在手机上会自行适配,因为这是在系统Launcher设置App形状后出现的.

相关文章:

  • 如何利用夜莺监控对Redis Cluster集群状态及集群中节点进行监控及告警?
  • JVM学习(五)--执行引擎
  • Manus AI突破多语言手写识别的技术壁垒的关键方法
  • Docker:容器化技术
  • 数据库MySQL进阶
  • 论文阅读笔记——Emerging Properties in Unified Multimodal Pretraining
  • 通过shell脚本检测服务是否存活并进行邮件的通知
  • 开源视频监控前端界面MotionEye
  • 视频剪辑 VEGAS - 配置视频片段保持原长宽比
  • 单片机中断系统工作原理及定时器中断应用
  • 【Excel 支持正则的方法】解决VBA引入正则的方法和步骤
  • Lesson 22 A glass envelope
  • 展示了一个三轴(X, Y, Z)坐标系!
  • 基于大模型的短暂性脑缺血发作预测与干预全流程系统技术方案大纲
  • 【C++】封装红黑树实现 mymap 和 myset
  • 记录将网站从http升级https
  • Linux(7)——进程(概念篇)
  • 万亿参数背后的算力密码:大模型训练的分布式架构与自动化运维全解析
  • 【RichTextEditor】 【分析2】RichTextEditor设置文字内容背景色
  • 毕业论文格式(Word)
  • 潍坊市做网站/seo工作职责
  • 杭州做网站哪个公司好/福建seo快速排名优化
  • 域名抢注网站源码/关键词排名优化技巧
  • 去哪里做网站比较好/高端网站建设的公司
  • 惠州做棋牌网站建设哪家好/百度搜索app下载
  • 做网站用百度百科的资料会侵权吗/苏州整站优化