How do launchers change the shape of an adaptive icon, including removal of background?

后端 未结 6 2065
难免孤独
难免孤独 2020-12-24 12:42

Background

Starting from Android O, apps can have adaptive icons, which are 2 layers of drawables: foreground and a background. The background is a mask that gets

6条回答
  •  天涯浪人
    2020-12-24 13:08

    I know two ways to build a custom shaped icon from an AdaptiveIconDrawable. I however think that Google should make a public AdaptiveIconDrawable.setMask(Path path) method:

    First way (pretty same way as AOSP code):

    public Bitmap createBitmap(@NonNull AdaptiveIconDrawable drawable, @NonNull Path path, int outputSize) {
    
        // make the drawable match the output size and store its bounds to restore later
        final Rect originalBounds = drawable.getBounds();
        drawable.setBounds(0, 0, outputSize, outputSize);
    
        // rasterize drawable
        final Bitmap outputBitmap = Bitmap.createBitmap(outputSize, outputSize, Bitmap.Config.ARGB_8888);
        final Canvas tmpCanvas = new Canvas(maskBitmap);
        drawable.getBackground().draw(tmpCanvas);
        drawable.getForeground().draw(tmpCanvas);
    
        // build a paint with shader composed by the rasterized AdaptiveIconDrawable
        final BitmapShader shader = new BitmapShader(outputBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
                Paint.FILTER_BITMAP_FLAG);
        paint.setShader(shader);
    
        // draw the shader with custom path (shape)
        tmpCanvas.drawPath(path, paint);
    
        // restore drawable original bounds
        drawable.setBounds(originalBounds);
    
        return outputBitmap;
    
    }
    

    Second way (the one I like most, because it allows to cache the mask bitmap in case of need using multiple times, avoiding Bitmap, Canvas, BitmapShader, and Paint re-allocation). If you don't understand, make sure you check this link out:

    @Nullable private Bitmap mMaskBitmap;
    @Nullable private Paint mClearPaint;
    
    @NonNull Canvas mCanvas = new Canvas();
    
    @Nullable Path mCustomShape; // your choice
    
    @Nullable Rect mOldBounds;
    
    public Bitmap createBitmap(@NonNull AdaptiveIconDrawable drawable, int outputSize) {
        final Bitmap outputBitmap = Bitmap.createBitmap(outputSize, outputSize, Bitmap.Config.ARGB_8888);
        mCanvas.setBitmap(outputBitmap);
    
        // rasterize the AdaptiveIconDrawable
        mOldBounds = drawable.getBounds();
        drawable.setBounds(0, 0, outputSize, outputSize);
        drawable.getBackground().draw(mCanvas);
        drawable.getForeground().draw(mCanvas);
    
        // finally mask the bitmap, generating the desired output shape
        // this clears all the pixels of the rasterized AdaptiveIconDrawable which
        // fall below the maskBitmap BLACK pixels
        final Bitmap maskBitmap = getMaskBitmap(mCustomShape, outputSize);
        mCanvas.drawBitmap(maskBitmap, 0, 0, mClearPaint);
    
        // restore original drawable bounds
        drawable.setBounds(mOldBounds);
    
        return outputBitmap;
    }
    
    // results a bitmap with the mask of the @path shape
    private Bitmap getMaskBitmap(@Nullable Path path, int iconSize) {
        if (mMaskBitmap != null && mMaskBitmap.getWidth() == iconSize && mMaskBitmap.getHeight() == iconSize)
            // quick return if already cached AND size-compatible
            return mMaskBitmap;
    
        // just create a plain, black bitmap with the same size of AdaptiveIconDrawable
        mMaskBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ALPHA_8);
        mMaskBitmap.eraseColor(Color.BLACK);
        final Canvas tmpCanvas = new Canvas(mMaskBitmap);
    
        // clear the pixels inside the shape (those where the icon will be visible)
        mClearPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
        if (path != null) 
            // if path is null, the output adaptive icon will not be masked (square, full size)
            tmpCanvas.drawPath(path, mClearPaint);
    
        return mMaskBitmap;
    }
    

    I prefer the second way, but the best one depends on the usage. If only one icon is shaped, then the first one would do the job. However, for multiple icons, the second one is better to go. Share your thoughts

提交回复
热议问题