Android 自定义View - 实现圆角图片的几种方式

  圆角图片在日常开发是非常常见,所以掌握它也是必要。之前也掌握相关有效的方法,但是从来没有总结过,今天写一篇文章来专门总结已知的方法。本文会介绍三种实现圆角图片的方法,同时也会介绍他们直接的区别。

1. clipPath方法

  第一种方法通过CanvasclipPath来实现,我们先来看一下相关实现代码:

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中添加了一个带圆角的矩形,所以后面绘制的图片自然而然就变成了圆角图片。
  这种方法的优缺点也是非常明显的:

  1. 优点:简单。
  2. 缺点:绘制出来的圆角图片带锯齿。这一点是非常不能忍,所以我们通常不会用此方式来实现圆角图片。

2. Xfermode方法

  接下来,我们来看一下第二种方式--通过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,平时我们调用的drawXXX方法所绘制的内容都是这个layer上面。而使用Xfermode需要注意的地方是,我们直接将内容绘制在这个默认的layer上面,而是必须绘制在一个新的layer上面,然后通过restore或者restoreToCount将新layer的内容绘制在默认的layer上面,如此操作才是正确的;除此之外,也可以通过新的bitmap来实现。具体的介绍,可以参考:Android PorterDuffXfermode使用中的一些坑
  而这种方法的优缺点呢?

  1. 优点:可定义高,支持多图层的圆角。
  2. 缺点:麻烦。

3. BitmapShader

  最后一种方法就是通过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);
  }
}

  具体就是上面的,至于为什么这样就能实现圆角,大家可以参考这篇文章:Android之Shader完全理解指南

  1. 优点:简单并且有效。
  2. 确定:仅支持Bitmap圆角,如果是LayerDrawable就显得手足无措。

5. 总结

  就目前而言,常见的实现圆角的方式就是上面三种,通常来说,我们选择第2种或者第3种方法,而第一种方法不推荐使用。
  而如果你的圆角要求仅限于纯图片,推荐第3种方式,因为比较简单;如果需要支持复杂的情形,比如LayerDrawable,推荐使用第2种方式。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 转载注明出处:http://www.jianshu.com/p/818e1284158d 1. 概述 Androi...
    王三的猫阿德阅读 13,565评论 10 49
  • 【Android 自定义View 圆形圆角图片】 基于Xfermode 实现 1、概述 在很久以前也写过一个利用X...
    Rtia阅读 11,834评论 1 10
  • 很久没写过东西,今天突然想写一个最近一直想记录的东西,相信很多猿友们也是很烦的一个东西,现在的设计的偏爱圆角,经常...
    草帽小子J阅读 9,708评论 1 6
  • 儿子中考成绩出来了,581分,不是很理想,离重点高中录取线有相差。但这分数进二中重点班应该不成问题。二中虽然比不上...
    伶丽阅读 1,345评论 0 0
  • 我曾写遍千言万语,甜言蜜语 用写的文字来代替我的谎言 这样以后有证据打我的脸 不是所有的花都被雨浇过,风吹过,太阳...
    导演张升志阅读 1,202评论 0 0