前言
实现圆角或圆形图片显示,我们开发中除了把原图直接做成圆角外,常见有三种方式实现:
使用Xfermode混合图层;
使用BitmapShader;
通过裁剪画布区域实现指定形状的图形(ClipPath)。
今天我就来带大家通过上面三种方式实现圆角或圆形的自定义View。
实现
1. 自定义属性
属性说明:
type: 圆角或圆形
src: 图片资源Id(这里使用的资源图片作为案例)
borderRadius: 圆角半径大小
2. 自定义View
我这里自定义了三个View, 大部分代码一致的,就是在onDraw方法实现不一样, 所以我这里就不重复贴出来了。
XfermodeCircleView、ClipPathCircleView 继承的View
BitmapShaderView 继承的ImageView
private static final int TYPE_CIRCLE = 0;
private static final int TYPE_ROUND = 1;
private int mRadius; //半径大小
private int mBorderRadius; //圆角大小
private int mWidth, mHeight; //控件宽高
private int mType; //圆角还是圆
private RectF mRoundRect; // 圆角矩阵
private BitmapShader mBitmapShader; //Shader
private Paint mBitmapPaint; //图片画笔
private Matrix mMatrix; //Bitmap缩放矩阵
public XfermodeCircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.XfermodeCircleView, defStyleAttr, 0);
this.mSrc = BitmapFactory.decodeResource(getResources(), a.getResourceId(R.styleable.XfermodeCircleView_src, 0));
this.mType = a.getInt(R.styleable.XfermodeCircleView_type, 0);
this.mBorderRadius = a.getDimensionPixelSize(R.styleable.XfermodeCircleView_borderRadius, 10);
a.recycle();
init();
}
XfermodeCircleView、ClipPathCircleView
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
//设置宽度
if (specMode == MeasureSpec.EXACTLY) { //match_parent
this.mWidth = specSize;
} else {
int imgWidth = this.getPaddingLeft() + this.mSrc.getWidth() + this.getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) { //wrap_content
this.mWidth = this.mHeight = Math.min(imgWidth, specSize);
} else {
this.mWidth = imgWidth;
}
}
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
//设置高度
if (specMode == MeasureSpec.EXACTLY) {
this.mHeight = specSize;
} else {
int imgHeight = this.getPaddingLeft() + this.mSrc.getHeight() + this.getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
this.mHeight = Math.min(imgHeight, specSize);
} else {
this.mHeight = imgHeight;
}
}
this.setMeasuredDimension(this.mWidth, this.mHeight);
BitmapShaderView
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (TYPE_CIRCLE == this.mType) { //如果宽高不一致,强制使用较小边
this.mWidth = this.mHeight = Math.min(this.getMeasuredWidth(), this.getMeasuredHeight());
this.mRadius = this.mWidth / 2;
this.setMeasuredDimension(this.mWidth, this.mWidth);
}
XfermodeCircleView
protected void onDraw(Canvas canvas) {
if (null == this.mSrc) return;
switch (this.mType) {
case TYPE_CIRCLE:
int size = Math.min(this.mWidth, this.mHeight);
this.mSrc = Bitmap.createScaledBitmap(this.mSrc, size, size, false);
canvas.drawBitmap(createCircleImageBitmap(this.mSrc, size), 0, 0, null);
break;
case TYPE_ROUND:
canvas.drawBitmap(createRoundCornerImage(this.mSrc), 0, 0, null);
break;
}
// super.onDraw(canvas);
}
private Bitmap createCircleImageBitmap(Bitmap source, int size) {
final Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap target = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(target);
canvas.drawCircle(size / 2, size / 2, size / 2, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(source, 0, 0, paint);
return target;
}
private Bitmap createRoundCornerImage(Bitmap source) {
final Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap target = Bitmap.createBitmap(this.mWidth, this.mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(target);
canvas.drawRoundRect(this.mRoundRect, this.mBorderRadius, this.mBorderRadius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(source, 0, 0, paint);
return target;
}
ClipPathCircleView
protected void onDraw(Canvas canvas) {
if (null == this.mSrc) return;
switch (this.mType) {
case TYPE_CIRCLE:
int size = Math.min(this.mWidth, this.mHeight);
this.mPath.reset();
this.mPath.addCircle(size / 2, size / 2, size / 2, Path.Direction.CCW);
canvas.clipPath(this.mPath);
this.mSrc = Bitmap.createScaledBitmap(this.mSrc, size, size, false);
canvas.drawBitmap(this.mSrc, 0, 0, this.mBitmapPaint);
break;
case TYPE_ROUND:
this.mPath.reset();
this.mPath.addRoundRect(this.mRectF, this.mBorderRadius, this.mBorderRadius, Path.Direction.CCW);
canvas.clipPath(this.mPath);
this.mSrc = Bitmap.createScaledBitmap(this.mSrc, this.getWidth(), this.getHeight(), false);
canvas.drawBitmap(this.mSrc, 0, 0, this.mBitmapPaint);
break;
}
// super.onDraw(canvas);
}
BitmapShaderView
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
if (null == this.getDrawable()) return;
this.setShader();
if (TYPE_CIRCLE == this.mType) {
canvas.drawCircle(this.mRadius, this.mRadius, this.mRadius, this.mBitmapPaint);
} else {
canvas.drawRoundRect(this.mRoundRect, this.mBorderRadius, this.mBorderRadius, this.mBitmapPaint);
}
}
private void setShader() {
Drawable drawable = this.getDrawable();
if (null == drawable) return;
Bitmap bmp = drawable2Bitmap(drawable);
this.mBitmapShader = new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
float scale = 1.0f;
if (TYPE_CIRCLE == this.mType) {
int bmpSize = Math.min(bmp.getWidth(), bmp.getHeight());
scale = this.mWidth * 1.0f / bmpSize;
} else if (TYPE_ROUND == this.mType) {
scale = Math.max(this.getWidth() * 1.0f / bmp.getWidth(), this.getHeight() * 1.0f / bmp.getHeight());
}
this.mMatrix.setScale(scale, scale);
this.mBitmapShader.setLocalMatrix(this.mMatrix);
this.mBitmapPaint.setShader(this.mBitmapShader);
}
/**
* Drawable 转 Bitmap
*
* @param drawable
* @return
*/
private Bitmap drawable2Bitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bd = (BitmapDrawable) drawable;
return bd.getBitmap();
}
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
drawable.setBounds(0, 0, w, h); //这里必须设置Bounds,否则无效果
drawable.draw(canvas);
return bmp;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (TYPE_ROUND == this.mType) {
this.mRoundRect = new RectF(0, 0, this.getWidth(), this.getHeight());
}
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superData = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putParcelable("super_data", superData);
bundle.putInt("type", this.mType);
bundle.putInt("border_radius", this.mBorderRadius);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
Parcelable superData = bundle.getParcelable("super_data");
this.mType = bundle.getInt("type", TYPE_CIRCLE);
this.mBorderRadius = bundle.getInt("border_radius", this.dp2px(10));
super.onRestoreInstanceState(superData);
} else
super.onRestoreInstanceState(state);
}
/**
* 动态设置圆角半径
*
* @param borderRadius
*/
public void setBorderRadius(int borderRadius) {
int pxVal = this.dp2px(borderRadius);
if (this.mBorderRadius != pxVal) {
this.mBorderRadius = pxVal;
this.invalidate();
}
}
/**
* 设置圆角类型
*
* @param type
*/
public void setType(int type) {
if (this.mType != type) {
this.mType = type;
if (this.mType != TYPE_ROUND && this.mType != TYPE_CIRCLE) {
this.mType = TYPE_CIRCLE;
}
this.requestLayout();
}
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.getResources().getDisplayMetrics());
}
结束完成…
补充:最后贴一下Glide4.+ 实现方式配置:
/**
* 加载圆角图片
*
* @param image
* @param view
*/
public static void loadRound(String image, ImageView view, int borderRadius) {
//设置图片圆角角度
RoundedCorners roundedCorners = new RoundedCorners(borderRadius);
//通过RequestOptions扩展功能,override:采样率,因为ImageView就这么大,可以压缩图片,降低内存消耗
RequestOptions options = RequestOptions.bitmapTransform(roundedCorners).override(300, 300);
Glide.with(BaseApp.getInstance()).load(image).apply(options).into(view);
}
/**
* 加载圆形图片
*
* @param image
* @param view
*/
public static void loadRound(String image, ImageView view, int width, int height, int borderRadius) {
//设置图片圆角角度
RoundedCorners roundedCorners = new RoundedCorners(borderRadius);
//通过RequestOptions扩展功能,override:采样率,因为ImageView就这么大,可以压缩图片,降低内存消耗
RequestOptions options = RequestOptions.bitmapTransform(roundedCorners).override(width, height);
Glide.with(BaseApp.getInstance()).load(image).apply(options).into(view);
}
总结
三种方式实现自定义圆角或圆形View基本代码在这里了,把上面的属性、方法、自定义属性组合起来就是三种自定义View,代码还需要很多优化的(如: 怎么加载网络图片, onDetachedFromWindow 中Bitmap的回收等等),这里主要通过这三个案例熟悉自定义基本过程。