How to have a circular, center-cropped imageView, without creating a new bitmap?

佐手、 提交于 2019-11-30 06:53:29

try this minimalist custom Drawable and modify it to meet your needs:

class D extends Drawable {
    Bitmap bitmap;
    Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    int side;
    float radius;

    public D(Bitmap wrappedBitmap) {
        bitmap = wrappedBitmap;
        borderPaint.setStyle(Paint.Style.STROKE);
        borderPaint.setStrokeWidth(16);
        borderPaint.setColor(0xcc220088);
        side = Math.min(bitmap.getWidth(), bitmap.getHeight());
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        Matrix matrix = new Matrix();
        RectF src = new RectF(0, 0, side, side);
        src.offset((bitmap.getWidth() - side) / 2f, (bitmap.getHeight() - side) / 2f);
        RectF dst = new RectF(bounds);
        dst.inset(borderPaint.getStrokeWidth(), borderPaint.getStrokeWidth());
        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);

        Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        shader.setLocalMatrix(matrix);
        maskPaint.setShader(shader);
        matrix.mapRect(src);
        radius = src.width() / 2f;
    }

    @Override
    public void draw(Canvas canvas) {
        Rect b = getBounds();
        canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), radius, maskPaint);
        canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), radius + borderPaint.getStrokeWidth() / 2, borderPaint);
    }

    @Override public void setAlpha(int alpha) {}
    @Override public void setColorFilter(ColorFilter cf) {}
    @Override public int getOpacity() {return PixelFormat.TRANSLUCENT;}
}

If I'm following you correctly, your Drawable class would be like so:

public class CroppedDrawable extends BitmapDrawable {
    private Path p = new Path();

    public CroppedDrawable(Bitmap b) {
        super(b);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);

        p.rewind();
        p.addCircle(bounds.width() / 2,
                    bounds.height() / 2,
                    Math.min(bounds.width(), bounds.height()) / 2,
                    Path.Direction.CW);
    } 

    @Override
    public void draw(Canvas canvas) {
        canvas.clipPath(p);
        super.draw(canvas);
    }
}

An example usage would be:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mila);
CroppedDrawable cd = new CroppedDrawable(bitmap);
imageView.setImageDrawable(cd);

Which, with your previous sample image, would give something like this:

As it seems, using "clipPath" isn't efficient and needs to have hardware-acceleration disabled on 4.3 and below.

A better solution is to use something like on this library, using BitmapShader :

https://github.com/hdodenhof/CircleImageView

which is based on :

http://www.curious-creature.com/2012/12/11/android-recipe-1-image-with-rounded-corners/

The relevant code is:

BitmapShader shader;
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);

RectF rect = new RectF(0.0f, 0.0f, width, height);

// rect contains the bounds of the shape
// radius is the radius in pixels of the rounded corners
// paint contains the shader that will texture the shape
canvas.drawRoundRect(rect, radius, radius, paint);

I still wish to know how to do it all in a drawable, if the input is a bitmap.

A quick draft of a CircleImageDrawable based on my CircleImageView library. This does not create a new Bitmap, uses a BitmapShader to achieve the desired effect and center-crops the image.

public class CircleImageDrawable extends Drawable {

    private final RectF mBounds = new RectF();
    private final RectF mDrawableRect = new RectF();
    private final RectF mBorderRect = new RectF();

    private final Matrix mShaderMatrix = new Matrix();
    private final Paint mBitmapPaint = new Paint();
    private final Paint mBorderPaint = new Paint();

    private int mBorderColor = Color.BLACK;
    private int mBorderWidth = 0;

    private Bitmap mBitmap;
    private BitmapShader mBitmapShader;
    private int mBitmapWidth;
    private int mBitmapHeight;

    private float mDrawableRadius;
    private float mBorderRadius;

    public CircleImageDrawable(Bitmap bitmap) {
        mBitmap = bitmap;
        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawCircle(mBounds.width() / 2.0f, mBounds.height() / 2.0f, mDrawableRadius, mBitmapPaint);
        if (mBorderWidth != 0) {
            canvas.drawCircle(mBounds.width() / 2.0f, mBounds.height() / 2.0f, mBorderRadius, mBorderPaint);
        }
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        mBounds.set(bounds);
        setup();
    }

    private void setup() {
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader);

        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);

        mBorderRect.set(mBounds);
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);

        mDrawableRect.set(mBorderRect);
        mDrawableRect.inset(mBorderWidth, mBorderWidth);
        mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);

        updateShaderMatrix();
        invalidateSelf();
    }

    private void updateShaderMatrix() {
        float scale;
        float dx = 0;
        float dy = 0;

        mShaderMatrix.set(null);

        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
            scale = mDrawableRect.height() / (float) mBitmapHeight;
            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
        } else {
            scale = mDrawableRect.width() / (float) mBitmapWidth;
            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
        }

        mShaderMatrix.setScale(scale, scale);
        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);

        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

    @Override
    public void setAlpha(int alpha) {
        mBitmapPaint.setAlpha(alpha);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mBitmapPaint.setColorFilter(colorFilter);
        invalidateSelf();
    }

    @Override
    public int getOpacity() {
        return 0;
    }

}

There is already a built-in way to accomplish this and it's 1 line of code (ThumbnailUtils.extractThumbnail())

int dimension = getSquareCropDimensionForBitmap(bitmap);
bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension); 

... 

//I added this method because people keep asking how  
//to calculate the dimensions of the bitmap...see comments below 
public int getSquareCropDimensionForBitmap(Bitmap bitmap)
{ 
    //If the bitmap is wider than it is tall 
    //use the height as the square crop dimension 
    if (bitmap.getWidth() >= bitmap.getHeight())
    { 
        dimension = bitmap.getHeight();
    } 
    //If the bitmap is taller than it is wide 
    //use the width as the square crop dimension 
    else 
    { 
        dimension = bitmap.getWidth();
    }  
} 

If you want the bitmap object to be recycled, you can pass options that make it so:

bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);

Below is the link for the documentation: ThumbnailUtils Documentation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!