圆角图片在日常开发中非常常见,所以掌握它也很必要。这里介绍三种实现圆角图片的方法,同时也会介绍他们的直接区别。
第一种方法通过Canvas的clipPath来实现,我们先来看一下相关实现代码:
public class RoundImageViewByClipPath extends ImageView {
private Path mPath;
private PaintFlagsDrawFilter mPaintFlagsDrawFilter;
private RectF mRectF;
private float[] mRadius = new float[]{100, 100, 100, 100, 100, 100, 100, 100};
public RoundImageViewByClipPath(Context context) {
this(context, null);
}
public RoundImageViewByClipPath(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundImageViewByClipPath(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public RoundImageViewByClipPath(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
mPath = new Path();
mPaintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
}
@Override
protected void onDraw(Canvas canvas) {
if (mRectF == null) {
mRectF = new RectF();
mRectF.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
mPath.addRoundRect(mRectF, mRadius, Path.Direction.CW);
canvas.setDrawFilter(mPaintFlagsDrawFilter);
canvas.clipPath(mPath);
super.onDraw(canvas);
}
}
clipPath方法会将绘制的图形裁剪下来,我们向Path中添加了一个带圆角的矩形,所以后面绘制的图片自然而然就变成了圆角图片。
这种方法的优缺点也非常明显:
- 优点:简单。
- 缺点:绘制出来的圆角图片带锯齿。这一点是非常不能忍,所以我们通常不会用此方式来实现圆角图片。
接下来,我们来看一下第二种方式,通过Xfermode
来实现圆角图片。我们先来看看代码实现:
public class RoundedImageViewByXfermode extends ImageView {
private Paint mPaint;
private Xfermode mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
private RectF mRectF;
private Bitmap mBitmap;
public RoundedImageViewByXfermode(Context context) {
this(context, null);
}
public RoundedImageViewByXfermode(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundedImageViewByXfermode(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public RoundedImageViewByXfermode(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.saveLayer(0, 0, getMeasuredWidth(), getMeasuredHeight(), null, Canvas.ALL_SAVE_FLAG);
Drawable drawable = getDrawable();
if (mBitmap == null) {
mBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas drawCanvas = new Canvas(mBitmap);
drawable.draw(drawCanvas);
}
if (mRectF == null) {
mRectF = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
mPaint.setXfermode(null);
canvas.drawRoundRect(mRectF, 100, 100, mPaint);
mPaint.setXfermode(mXfermode);
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
canvas.restore();
}
}
其实呢,使用Xfermode
实现圆角图片的原理非常简单,先绘制Dst图层和Src图层,然后根据谷歌爸爸给定的规则设置不同的Xfermode,从而达到裁剪图层的效果:
使用Xfermode
通常来说绘制两层,第一层称为DST图层,第二层称为src图层,然后根据Xfermode
来去计算两个图层的关系。
canvas有一个默认的layer,平时我们调用的drawXXXX方法所绘制的内容都是这个layer上面。而使用Xfermode需要注意的地方时,我们不是直接将内容绘制在这个默认的layer上面,而是必须绘制在一个新的layer上面,然后restore或者resyoreToCount将新layer的内容绘制在默认的layer上面,如此操作才是正确的;除此之外也可以通过新的bitma来实现。
这种方法的优缺点呢?
- 有点:可定义高,支持多图层的圆角。
- 缺点:麻烦。
最后一种办法就是通过BitmapShader
方法来使先,这种方法也是极力推荐,如果你的要求仅仅是图片实现圆角,推荐这种方法,为什么呢?先看看相关实现:
public class RoundImageViewByBitmapShader extends ImageView {
private Shader mShader;
private Paint mPaint;
public RoundImageViewByBitmapShader(Context context) {
super(context);
}
public RoundImageViewByBitmapShader(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public RoundImageViewByBitmapShader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public RoundImageViewByBitmapShader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onDraw(Canvas canvas) {
BitmapDrawable drawable = (BitmapDrawable) getDrawable();
if (mShader == null) {
mShader = new BitmapShader(drawable.getBitmap(), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
}
mPaint.setShader(mShader);
canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), 100, 100, mPaint);
}
}
具体就是上面的,至于为什么可以实现圆角,可以参考Shader完全理解指南。
- 优点:简单并且有效。
- 确定:仅支持Bitmap圆角,如果是
LayerDrawable
就显得手足无措。
就目前而言,常见的实现圆角的方式就是上面三种,通常来说,我们选择第2种或者第3种方法,而第一种方法不推荐使用。
而如果你的圆角要求仅限于纯图片,推荐第3种方式,因为比较简单;如果需要支持复杂的情形,比如LayerDrawable
,推荐使用第2种方式。