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

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形状后出现的.

相关文章:

Android12 launcher3修改App图标白边问题

Android12 launcher3修改App图标白边问题 1.前言&#xff1a; 今天在Android12 Rom定制客制化系统应用时发现改变系统App图标的形状会出现一个问题&#xff0c;那就是图标被缩小了&#xff0c;没有显示完整&#xff0c;有一个白边&#xff0c;这在普通的App开发很少遇到&…...

【iOS】分类、扩展、关联对象

分类、扩展、关联对象 前言分类扩展扩展和分类的区别关联对象key的几种用法流程 总结 前言 最近的学习中笔者发现自己对于分类、扩展相关知识并不是很熟悉&#xff0c;刚好看源码类的加载过程中发现有类扩展与关联对象详解。本篇我们来探索一下这部分相关知识&#xff0c;首先…...

内蒙古工程系列建设工程技术人才评审条件

关于印发《内蒙古自治区工程系列建设工程专业技术人才职称评审条件》的通知 内蒙古工程系列建设工程技术人才评审条件适用范围 内蒙古工程系列建设工程技术人才评审条件之技术员评审要求 内蒙古工程系列建设工程技术人才评审条件之助理工程师评审要求 内蒙古工程系列建设工程技…...

Elasticsearch超详细安装部署教程(Windows Linux双系统)

文章目录 一、前言二、Windows系统安装部署2.1 环境准备2.2 Elasticsearch安装2.3 安装为Windows服务2.4 Head插件安装2.5 Kibana集成&#xff08;可选&#xff09; 三、Linux系统安装部署3.1 环境准备3.2 Elasticsearch安装3.3 系统优化3.4 启动服务3.5 安全配置&#xff08;可…...

第十六章:数据治理之数据架构:数据模型和数据流转关系

本章我们说一下数据架构&#xff0c;说到数据架构&#xff0c;就很自然的想到企业架构、业务架构、软件架构&#xff0c;因为个人并没有对这些内容进行深入了解&#xff0c;所以这里不做比对是否有相似或者共通的地方&#xff0c;仅仅来说一下我理解的数据架构。 1、什么是架构…...

目标检测DINO-DETR(2023)详细解读

文章目录 对比去噪训练混合查询选择look forward twice 论文全称为&#xff1a;DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection 提出了三个新的方法&#xff1a; 首先&#xff0c;为了改进一对一的匹配效果&#xff0c;提出了一种对比去噪训练方法…...

基于 STM32 的蔬菜智能育苗系统硬件与软件设计

一、系统总体架构 蔬菜智能育苗系统通过单片机实时采集温湿度、光照等环境数据,根据预设阈值自动控制灌溉、补光、通风等设备,实现育苗环境的智能化管理。系统主要包括以下部分: 主控芯片:STM32F103C8T6(32 位 ARM Cortex-M3 单片机,性价比高,适合嵌入式控制)传感器模…...

实现一个带有授权码和使用时间限制的Spring Boot项目

生成和验证授权码记录授权时间和过期时间实现授权逻辑 以下是具体的实现方法&#xff1a; 1. 生成和验证授权码 可以使用加密技术生成和验证授权码。授权码中可以包含有效期等信息&#xff0c;并使用密钥进行签名。 示例代码&#xff1a; java复制代码 import javax.crypt…...

SGlang 推理模型优化(PD架构分离)

一、技术背景 随着大型语言模型&#xff08;LLM&#xff09;广泛应用于搜索、内容生成、AI助手等领域&#xff0c;对模型推理服务的并发能力、响应延迟和资源利用效率提出了前所未有的高要求。与模型训练相比&#xff0c;推理是一个持续进行、资源消耗巨大的任务&#xff0c;尤…...

TuyaOpen横空出世!涂鸦智能如何用开源框架重构AIoT开发范式?

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引子&#xff1a;AIoT开发的“不可能三角”被打破 当AI与物理世界深度融合的浪潮席卷全球&#xff0c;开发者们却始终面临一个“不可能三角”——开发…...

Vue语法【2】

1.插值表达式&#xff1a; 语法规则&#xff1a; {{Vue实例中data的变量名}}使用场景&#xff1a; 插值表达式一般使用在文本内容中&#xff0c;如果是元素的属性内容中则无法使用&#xff1b; 案例&#xff1a; <!DOCTYPE html> <html lang"en"> &l…...

2.2.1 05年T2

引言 本文将从一预习、二自习、三学习、四复习等四个阶段来分析2005年考研英语阅读第二篇文章。为了便于后续阅读&#xff0c;我将第四部分复习放在了首位。 四、复习 方法&#xff1a;错误思路分析总结考点文章梳理 4.1 错题分析 题目&#xff1a;26&#xff08;细节题&…...

每日c/c++题 备战蓝桥杯(修理牛棚 Barn Repair)

修理牛棚 Barn Repair 题解 问题背景与挑战 在一个暴风雨交加的夜晚&#xff0c;Farmer John 的牛棚遭受了严重的破坏。屋顶被掀飞&#xff0c;大门也不翼而飞。幸运的是&#xff0c;许多牛正在度假&#xff0c;牛棚并未住满。然而&#xff0c;为了保护那些还在牛棚里的牛&am…...

6个月Python学习计划 Day 3

&#x1f3af; 今日目标 掌握 while 和 for 循环的使用方式理解 range() 的工作机制实践&#xff1a;打印 1~100、累加、九九乘法表等常见程序逻辑 &#x1f9e0; 学习内容详解 while 循环 i 1 while i < 5:print(f"第 {i} 次循环")i 1&#x1f4cc; 特点&…...

Linux虚拟文件系统(2)

2.3 目录项-dentry 目录项&#xff0c;即 dentry&#xff0c;用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项&#xff0c;就构成了文件系统的目录结构。和上一章中超级块和索引节点不同&#xff0c;目录项并不是实际存在于磁盘上的&#xff0c…...

【数据结构】栈和队列(上)

目录 一、栈&#xff08;先进后出、后进先出的线性表&#xff09; 1、栈的概念及结构 2、栈的底层结构分析 二、代码实现 1、定义一个栈 2、栈的初始化 3、入栈 3、增容 4、出栈 5、取栈顶 6、销毁栈 一、栈&#xff08;先进后出、后进先出的线性表&#xff09; 1、…...

科技赋能·长效治理|无忧树建筑修缮渗漏水长效治理交流会圆满举行!

聚焦行业痛点&#xff0c;共话长效未来&#xff01;5月16日&#xff0c;由无忧树主办的主题为“科技赋能长效治理”的建筑修缮渗漏水长效治理技术交流会在上海圆满举行。来自全国的建筑企业代表、专家学者、技术精英齐聚一堂&#xff0c;共探渗漏治理前沿技术&#xff0c;见证科…...

【闲聊篇】java好丰富!

1、在学习mybatis-plus的文档时&#xff0c;发现引入了solon依赖&#xff0c;才发现这是一个对标spring生态的框架&#xff0c;有意思&#xff01; 还有若依框架&#xff0c;真的好丰富~~~~~~~ 2、今天面试官问我&#xff0c;他说很少遇到用redission做延迟队列的。后面我就反…...

STL中list的模拟

这里写目录标题 list 的节点 —— ListNodelist 的 “导览员” —— ListIteratorlist 的核心 —— list 类构造函数迭代器相关操作容量相关操作 结尾 在 C 的 STL&#xff08;标准模板库&#xff09;中&#xff0c;list 是一个十分重要的容器&#xff0c;它就像一个灵活的弹簧…...

6.3.2图的深度优先遍历

知识总览&#xff1a; 树的先根遍历&#xff1a; 采用递归一直找某个节点的子树直到找不到从上往下找 访问根节点1&#xff0c;1的子树有2、3、4,访问2&#xff0c;2节点子树有5访问5,5没有子树&#xff0c;退回到2,2还有子树6访问6,6没有子树再退回到2,2的子树都被访问了再退…...

畅游Diffusion数字人(30):情绪化数字人视频生成

畅游Diffusion数字人(0):专栏文章导航 前言:仅从音频生成此类运动极具挑战性,因为它在音频和运动之间存在一对多的相关性。运动视频的情绪是多元化的选择,之前的工作很少考虑情绪化的数字人生成。今天解读一个最新的工作FLOAT,可以生成制定情绪化的数字人视频。 目录 贡献…...

UE5 Va Res发送请求、处理请求、json使用

文章目录 介绍发送一个Get请求发送Post请求设置请求头请求体带添json发送请求完整的发送蓝图 处理收到的数据常用的json处理节点 介绍 UE5 自带的Http插件&#xff0c;插件内自带json解析功能 发送一个Get请求 只能写在事件图表里 发送Post请求 只能写在事件图表里 设置…...

关于flutter中Scaffold.of(context).openEndDrawer();不生效问题

原因&#xff1a; 在 Flutter 中&#xff0c;Scaffold.of(context) 会沿着当前的 context 向上查找最近的 Scaffold。如果当前的 widget 树层级中没有合适的 Scaffold&#xff08;比如按钮所在的 context 是在某个子 widget 中&#xff09;&#xff0c;就找不到它。 解决办法…...

【C++】深入理解C++中的函数与运算符重载

文章目录 前言一、什么是重载&#xff1f;1.1 函数重载1.1.1 函数重载的规则1.1.2 示例&#xff1a;函数重载 1.2 运算符重载1.2.1 运算符重载的规则1.2.2 示例&#xff1a;运算符重载 1.2.3 运算符重载的注意事项 二、重载的注意事项2.1 重载的二义性2.2 默认参数和重载2.3 运…...

【读代码】BAGEL:统一多模态理解与生成的模型

一、项目概览 1.1 核心定位 BAGEL是字节跳动推出的开源多模态基础模型,具有70亿激活参数(140亿总参数)。该模型在统一架构下实现了三大核心能力: 多模态理解:在MME、MMBench等9大评测基准中超越Qwen2.5-VL等主流模型文本生成图像:生成质量媲美SD3等专业生成模型智能图像…...

隧道自动化监测解决方案

行业现状 隧道作为一种重要的交通运输通道&#xff0c;不管是缓解交通压力&#xff0c;还是让路网结构更趋于完善&#xff0c;它都有着不可估量的作用。隧道在运营过程中&#xff0c;由于受到材料退化、地震、人为因素等影响会发生隧道主体结构的损坏和劣化。若不及时检修和维护…...

如何通过EventChannel实现Flutter与原生平台的双向通信?

在Flutter开发中,EventChannel是处理单向数据流的核心组件,尤其适用于原生平台(Android/iOS)主动向Flutter端推送实时数据的场景,例如传感器数据、后台任务通知等。虽然EventChannel本身以原生到Flutter的单向通信为主,但结合特定设计模式,仍可实现双向交互。本文将详细…...

游戏引擎学习第307天:排序组可视化

简短谈谈直播编程的一些好处。 上次结束后&#xff0c;很多人都指出代码中存在一个拼写错误&#xff0c;因此这次我们一开始就知道有一个 bug 等待修复&#xff0c;省去了调试寻找错误的时间。 今天的任务就是修复这个已知 bug&#xff0c;然后继续排查其他潜在的问题。如果短…...

java接口自动化初识

简介 了解什么是接口和为什么要做接口测试。并且知道接口自动化测试应该学习哪些技术以及接口自动化测试的落地过程。 一、什么是接口 在这里我举了一个比较生活化的例子&#xff0c;比如我们有一台笔记本&#xff0c;在笔记本的两端有很多插口。例如&#xff1a;USB插口。那…...

工作流引擎-01-Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎,支持现实世界的流程自动化需求

前言 大家好&#xff0c;我是老马。 最近想设计一款审批系统&#xff0c;于是了解一下关于流程引擎的知识。 下面是一些的流程引擎相关资料。 工作流引擎系列 工作流引擎-00-流程引擎概览 工作流引擎-01-Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎&#x…...