前言
实现圆角或圆形图片显示,我们开发中除了把原图直接做成圆角外,常见有三种方式实现:
使用Xfermode混合图层;
使用BitmapShader;
通过裁剪画布区域实现指定形状的图形(ClipPath)。
今天我就来带大家通过上面三种方式实现圆角或圆形的自定义View。
先来张效果图:
实现
1. 自定义属性
<attr name="borderRadius" format="dimension" /> <attr name="type"> <enum name="circle" value="0" /> <enum name="round" value="1" /> </attr> <attr name="src" format="reference"/> <declare-styleable name="XfermodeCircleView"> <attr name="borderRadius" /> <attr name="type" /> <attr name="src" /> </declare-styleable>
属性说明:
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的回收等等),这里主要通过这三个案例熟悉自定义基本过程。