如何有一个圆形的,中心裁剪的imageView,而不创build一个新的位图?

:我知道有很多关于这个问题的问题和存储库,但似乎没有一个适合我尝试实现的东西。

背景

给定一个任何纵横比位图,我希望将其设置为ImageView的内容(仅使用drawable而不扩展ImageView),以便内容将被中心裁剪,然后以圆形。

所有这一切,只需最less的内存使用,因为图像可能会相当大。 我不想为此创build一个全新的位图。 内容已经在那里…

问题

我发现的所有解决scheme都缺less我写的东西:有些不是中心裁剪,有的假设图像是方形的,有的是从给定的位图创build一个新的位图。

我试过了

除了尝试各种知识库之外,我已经尝试了这个教程 ,并试图解决非方形长宽比的情况,但是我失败了。

这里是它的代码,以防止网站被closures:

public class RoundImage extends Drawable { private final Bitmap mBitmap; private final Paint mPaint; private final RectF mRectF; private final int mBitmapWidth; private final int mBitmapHeight; public RoundImage(Bitmap bitmap) { mBitmap = bitmap; mRectF = new RectF(); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setDither(true); final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mPaint.setShader(shader); mBitmapWidth = mBitmap.getWidth(); mBitmapHeight = mBitmap.getHeight(); } @Override public void draw(Canvas canvas) { canvas.drawOval(mRectF, mPaint); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); mRectF.set(bounds); } @Override public void setAlpha(int alpha) { if (mPaint.getAlpha() != alpha) { mPaint.setAlpha(alpha); invalidateSelf(); } } @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public int getIntrinsicWidth() { return mBitmapWidth; } @Override public int getIntrinsicHeight() { return mBitmapHeight; } public void setAntiAlias(boolean aa) { mPaint.setAntiAlias(aa); invalidateSelf(); } @Override public void setFilterBitmap(boolean filter) { mPaint.setFilterBitmap(filter); invalidateSelf(); } @Override public void setDither(boolean dither) { mPaint.setDither(dither); invalidateSelf(); } public Bitmap getBitmap() { return mBitmap; } } 

我发现一个非常好的解决scheme( 这里 )正是我所需要的,除了它在ImageView本身中使用它,而不是创build一个drawable。 这意味着我无法将其设置为例如视图的背景。

这个问题

我怎样才能做到这一点?


编辑:这是当前的代码,因为我想添加边框,它也有这个代码:

 public class SimpleRoundedDrawable extends BitmapDrawable { private final Path p = new Path(); private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); public SimpleRoundedDrawable(final Resources res, final Bitmap bitmap) { super(res, bitmap); mBorderPaint.setStyle(Paint.Style.STROKE); } public SimpleRoundedDrawable setBorder(float borderWidth, @ColorInt int borderColor) { mBorderPaint.setStrokeWidth(borderWidth); mBorderPaint.setColor(borderColor); invalidateSelf(); return this; } @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); final float width = getBounds().width(), height = getBounds().height(); canvas.drawCircle(width / 2, height / 2, Math.min(width, height) / 2, mBorderPaint); } } 

我希望这是如何真正的工作。


编辑:似乎该解决scheme只适用于特定的Android版本,因为它不适用于Android 4.2.2。 相反,它显示一个平方的图像。

编辑:似乎上述解决scheme也比使用BitmapShader效率(链接在这里 )低得多。 知道如何在drawable中使用它,而不是在定制的ImageView中使用它真的很棒

– 以下是以下解决scheme的当前修改版本。 我希望这对一些人来说很方便:

 public class SimpleRoundedDrawable extends Drawable { final Paint mMaskPaint = new Paint(Paint.ANTI_ALIAS_FLAG), mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); Bitmap mBitmap; int mSide; float mRadius; public SimpleRoundedDrawable() { this(null); } public SimpleRoundedDrawable(Bitmap bitmap) { this(bitmap, 0, 0); } public SimpleRoundedDrawable(Bitmap bitmap, float width, @ColorInt int color) { mBorderPaint.setStyle(Paint.Style.STROKE); mBitmap = bitmap; mSide = mBitmap == null ? 0 : Math.min(bitmap.getWidth(), bitmap.getHeight()); mBorderPaint.setStrokeWidth(width); mBorderPaint.setColor(color); } public SimpleRoundedDrawable setBitmap(final Bitmap bitmap) { mBitmap = bitmap; mSide = Math.min(bitmap.getWidth(), bitmap.getHeight()); invalidateSelf(); return this; } public SimpleRoundedDrawable setBorder(float width, @ColorInt int color) { mBorderPaint.setStrokeWidth(width); mBorderPaint.setColor(color); invalidateSelf(); return this; } @Override protected void onBoundsChange(Rect bounds) { if (mBitmap == null) return; Matrix matrix = new Matrix(); RectF src = new RectF(0, 0, mSide, mSide); src.offset((mBitmap.getWidth() - mSide) / 2f, (mBitmap.getHeight() - mSide) / 2f); RectF dst = new RectF(bounds); final float strokeWidth = mBorderPaint.getStrokeWidth(); if (strokeWidth > 0) dst.inset(strokeWidth, strokeWidth); matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER); Shader shader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); shader.setLocalMatrix(matrix); mMaskPaint.setShader(shader); matrix.mapRect(src); mRadius = src.width() / 2f; } @Override public void draw(Canvas canvas) { Rect b = getBounds(); if (mBitmap != null) canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), mRadius, mMaskPaint); final float strokeWidth = mBorderPaint.getStrokeWidth(); if (strokeWidth > 0) canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), mRadius + strokeWidth / 2, mBorderPaint); } @Override public void setAlpha(int alpha) { mMaskPaint.setAlpha(alpha); invalidateSelf(); } @Override public void setColorFilter(ColorFilter cf) { mMaskPaint.setColorFilter(cf); invalidateSelf(); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } } 

Solutions Collecting From Web of "如何有一个圆形的,中心裁剪的imageView,而不创build一个新的位图?"

试试这个简约的自定义Drawable并修改它以满足您的需求:

 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;} } 

如果我正确地跟着你,你的Drawable类会是这样的:

 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); } } 

一个示例用法是:

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

其中,与您以前的示例图像,会给这样的事情:

截图

基于我的CircleImageView库的CircleImageDrawable快速草稿。 这不会创build一个新的位图,使用一个BitmapShader来达到所需的效果和中心裁剪图像。

 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; } } 

看起来,使用“clipPath”效率不高,需要在4.3及更低版本上禁用硬件加速。

更好的解决scheme是使用BitmapShader这个库上的东西:

https://github.com/hdodenhof/CircleImageView

这是基于:

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

相关的代码是:

 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); 

如果input是位图,我仍然希望知道如何在drawable中完成所有操作。

已经有一个内置的方法来完成这个,它是1行代码(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(); } } 

如果你想要回收位图对象,你可以传递如下的选项:

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

以下是该文档的链接: ThumbnailUtils文档