注意:我知道有很多关于此的问题和存储库,但似乎没有一个符合我的目标。
给定任何宽高比的位图,我希望将其设置为ImageView的内容(仅使用drawable,而不扩展ImageView),以便内容将被中心裁剪,但形状一圈。
所有这一切,内存使用量最少,因为有时图像可能非常大。我不想为此创建一个全新的Bitmap。内容已经存在......
我发现的所有解决方案都缺少我写过的内容之一:有些不是中心裁剪,有些人认为图像是方形的,有些则是根据给定的位图创建新的位图。
除了尝试各种存储库之外,我已尝试过this tutorial,我试图针对非方形宽高比进行修复,但我已经失败了。
以下是其代码,以防网站关闭:
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;
}
}
我发现的一个非常好的解决方案(here)正是我所需要的,除了它在ImageView本身中使用它,而不是创建一个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);
}
}
我希望这就是事情应该如何发挥作用。
编辑:似乎该解决方案仅适用于特定的Android版本,因为它不适用于Android 4.2.2。相反,它显示了平方图像。编辑:似乎上述解决方案的效率远低于使用BitmapShader(链接here)。知道如何在drawable中使用它而不是在自定义的ImageView中使用它真的很棒
- 这是以下解决方案的当前修改版本。我希望这对某些人来说会很方便:
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;
}
}
答案 0 :(得分:3)
如果我正确地关注您,您的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);
使用之前的示例图像,可以得到如下内容:
答案 1 :(得分:3)
尝试这种极简主义自定义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;}
}
答案 2 :(得分:2)
基于我的CircleImageView库的CircleImageDrawable的快速草稿。这不会创建新的Bitmap,使用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;
}
}
答案 3 :(得分:1)
看起来,使用“clipPath”效率不高,需要在4.3及以下禁用硬件加速。
更好的解决方案是使用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);
如果输入是位图,我仍然想知道如何在drawable中完成所有操作。
答案 4 :(得分:0)
已经有一种内置方法可以实现这一点,它有一行代码(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 Documentation