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,从而达到裁剪图层的效果。也就是下面这张图片:

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

  使用 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种方式。

你可能感兴趣的:(Android 自定义View - 实现圆角图片的几种方式)