android 图片圆角 遮罩_Android 圆角、圆形 ImageView 实现

一、 特点

基于AppCompatImageView扩展

支持圆角、圆形显示

可绘制边框,圆形时可绘制内外两层边框

支持边框不覆盖图片

可绘制遮罩

......

二、基本原理

我们要实现的图片控件继承自AppCompatImageView,它是ImageView的子类,但提供了更好的兼容性,我们在此基础上添加了若干自定义的属性和方法以实现最终的 NiceImageView:

public class NiceImageView extends AppCompatImageView {

......

}

要实圆角或者圆形的显示效果,就是对图片显示的内容区域进行“裁剪”,只显示指定的区域即可。如何做呢?

一种比较直接的办法是这样的,由于图片是被绘制在画布上的,所以用canvas 的 clipPath()方法先将画布裁剪成指定形状,这样就能让图片按指定形状显示了,重新draw()方法即可:

@Override

public void draw(Canvas canvas) {

canvas.save();

canvas.clipPath(path);

super.draw(canvas);

canvas.restore();

}

这样使用src、background属性给ImageView设置显示的图片都能达到预期的显示效果。但是由于clipPath()方法不支持抗锯齿,图片边缘会有明显的毛糙感,体验并不理想,所以需要寻找其它方法。

另一种方法是使用图像的 Alpha 合成模式,即

PorterDuff 来实现,官方文档。这里我们使用其中的DST_IN模式。整个过程就是先绘制目标图像,也就是图片;再绘制原图像,即一个圆角矩形或者圆形,这样最终目标图像只显示和原图像重合的区域。

xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);

@Override

protected void onDraw(Canvas canvas) {

// 使用离屏缓存,新建一个srcRectF区域大小的图层

canvas.saveLayer(srcRectF, null, Canvas.ALL_SAVE_FLAG);

// ImageView自身的绘制流程,即绘制图片

super.onDraw(canvas);

// 给path添加一个圆角矩形或者圆形

if (isCircle) {

path.addCircle(width / 2.0f, height / 2.0f, radius, Path.Direction.CCW);

} else {

path.addRoundRect(srcRectF, srcRadii, Path.Direction.CCW);

}

paint.setAntiAlias(true);

// 画笔为填充模式

paint.setStyle(Paint.Style.FILL);

// 设置混合模式

paint.setXfermode(xfermode);

// 绘制path

canvas.drawPath(path, paint);

// 清除Xfermode

paint.setXfermode(null);

// 恢复画布状态

canvas.restore();

}

到这里就实现了显示为圆角或者圆形了。但是需要通过src属性或者对应的方法来设置图片,否则不能达到预期效果。

三、绘制边框

绘制边框就相对容易理解了,只需要绘制一个指定样式的圆角矩形或者圆形即可:

private void drawBorders(Canvas canvas) {

if (isCircle) {

if (borderWidth > 0) {

drawCircleBorder(canvas, borderWidth, borderColor, radius - borderWidth / 2.0f);

}

if (innerBorderWidth > 0) {

drawCircleBorder(canvas, innerBorderWidth, innerBorderColor, radius - borderWidth - innerBorderWidth / 2.0f);

}

} else {

if (borderWidth > 0) {

drawRectFBorder(canvas, borderWidth, borderColor, borderRectF, borderRadii);

}

}

}

private void drawCircleBorder(Canvas canvas, int borderWidth, int borderColor, float radius) {

initBorderPaint(borderWidth, borderColor);

path.addCircle(width / 2.0f, height / 2.0f, radius, Path.Direction.CCW);

canvas.drawPath(path, paint);

}

private void drawRectFBorder(Canvas canvas, int borderWidth, int borderColor, RectF rectF, float[] radii) {

initBorderPaint(borderWidth, borderColor);

path.addRoundRect(rectF, radii, Path.Direction.CCW);

canvas.drawPath(path, paint);

}

private void initBorderPaint(int borderWidth, int borderColor) {

path.reset();

// 设置画笔为描边模式

paint.setStyle(Paint.Style.STROKE);

// 描边宽度

paint.setStrokeWidth(borderWidth);

// 描边颜色

paint.setColor(borderColor);

}

当图片显示为圆形时,还可以绘制一个内边框,但圆角矩形的话由于圆角大小的问题,目前只能设置一个边框咯。

但是有个问题,绘制的边框会覆盖在图片上,如果边框太宽会导致图片的可见区域变小了,影像显示效果,像这样,左下角的花盆不见了:

那么如何让边框不覆盖在图片上呢?可以在 Alpha 合成绘制前先将画布缩小一定比例,最后再绘制边框,这样问题就解决了。

@Override

protected void onDraw(Canvas canvas) {

canvas.saveLayer(srcRectF, null, Canvas.ALL_SAVE_FLAG);

// 缩小画布

if (!isCoverSrc) {

float sx = 1.0f * (width - 2 * borderWidth - 2 * innerBorderWidth) / width;

float sy = 1.0f * (height - 2 * borderWidth - 2 * innerBorderWidth) / height;

// 缩小画布,使图片内容不被border、padding覆盖

canvas.scale(sx, sy, width / 2.0f, height / 2.0f);

}

......

canvas.restore();

// 绘制边框

drawBorders(canvas);

}

缩放后的ImageView显示区域的宽高就是原宽、高分别减去2倍的边框宽度,这样缩小的比例也就显而易见了。效果如下,左下角的花盆出来了:

四、绘制遮罩

遮罩可以理解为一层带透明度的颜色,遮罩默认不绘制,当制定了遮罩颜色时才会绘制,实现很简单:

@Override

protected void onDraw(Canvas canvas) {

......

// 绘制遮罩

if (maskColor != 0) {

paint.setColor(maskColor);

canvas.drawPath(path, paint);

}

canvas.restore();

drawBorders(canvas);

}

例如加一个透明度30%的红色遮罩后的效果:

核心的实现逻辑就这些了,剩下的就是自定义属性和方法了,有兴趣的可以看源码,都很简单,希望对你有所帮助吧!

五、其它

如果你需要实现类似钉钉的圆形组合头像,例如:

可以先生成对应的Bitmap,并用圆形的 NiceImageView 显示即可。如何生成组合Bitmap可以参考这里:CombineBitmap。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值