上篇文章 介绍了 Fresco 基础使用和实现图片圆角的方法,可以通过两种方式来实现圆角:BITMAP_ONLY 模式和 OVERLAY_COLOR 模式。本文通过分析 Fresco 源码来介绍这两种方式实现圆角的原理,并总结 Android 中常用的实现图片圆角的方法。
本文重点分析 Fresco 中实现图片圆角的源码,其他部分的源码,将在后续文章中介绍。
Fresco 中圆角实现原理
在 com.facebook.drawee.drawable 包中有如下文件
- Rounded.java
- RoundedBitmapDrawable.java
- RoundedColorDrawable.java
- RoundedCornersDrawable.java
其中 Rounded 是圆角实现类的接口,定义了圆角类实现的方法:
public interface Rounded {
void setCircle(boolean isCircle);
void setRadius(float radius);
void setRadii(float[] radii);
void setBorder(int color, float width);
}
其他三个类实现了 Rounded 接口,来实现两种不同模式的圆角,RoundedCornersDrawable.java 用于实现 OVERLAY_COLOR 模式的圆角,而 RoundedBitmapDrawable.java 和 RoundedCorlorDrawable.java 都是用于实现 BITMAP_ONLY 模式的圆角,两者的区别在于传入的资源类型不同,前者是对 BitmapDrawable 进行圆角处理,而后者是对 ColorDrawable 进行处理。
了解了源码中实现图片圆角的结构,下面开始进入到具体的代码中了解具体的实现过程
BITMAP_ONLY 模式
作为默认的实现模式,首先来了解下这种模式的实现过程
进入到 RoundedBitmapDrawable.java 中,首先看它的绘制过程,找到 draw() 方法:
@Override
public void draw(Canvas canvas) {
updateTransform();//更新图片变换矩阵
updateNonzero();//更新 0 值,判断有没有 设置圆形,圆角,边框等属性
if (!mIsNonzero) {
//如果没有设置以上属性,则 mIsNonzero 返回 false,直接调用父类的绘制
super.draw(canvas);
return;
}
updatePath();//更新 Path
updatePaint();//更新画笔
int saveCount = canvas.save();//保存画布状态
canvas.concat(mInverseTransform);//设置变换矩阵
canvas.drawPath(mPath, mPaint);//绘制 Path
if (mBorderWidth != 0) {
//绘制边框
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setColor(DrawableUtils.multiplyColorAlpha(mBorderColor, mPaint.getAlpha()));
canvas.drawPath(mPath, mBorderPaint);
}
canvas.restoreToCount(saveCount);//合并图像
}
从 draw() 方法可以了解到圆角图片的绘制过程:
- 更新变换矩阵,用于图片大小缩放适配
- 判断有没有设置属性,如果没有则直接绘制,如果有则进行下一步
- 更新 Path,根据属性确定绘制的形状
- 更新 Paint,将图片资源填充到画笔
- 绘制图片,绘制边框
本文的重点是了解圆角的实现过程,所以接下来就进入到 updatePath() 和 updatePaint() 中看看 Path 和 Paint 是怎样实现圆角的
private void updatePath() {
if (mIsPathDirty) {
//大概是说如果有对图片进行设置
mPath.reset();//重置 Path
mRootBounds.inset(mBorderWidth/2, mBorderWidth/2);//矩形向内缩进半个边框宽度,避免边框遮挡图片
if (mIsCircle) {
//如果设置为圆形图片,则 Path 设置为圆形,否则就设置为矩形
mPath.addCircle(
mRootBounds.centerX(),
mRootBounds.centerY(),
Math.min(mRootBounds.width(), mRootBounds.height())/2,
Path.Direction.CW);
} else {
mPath.addRoundRect(mRootBounds, mCornerRadii, Path.Direction.CW);
}
mRootBounds.inset(-(mBorderWidth/2), -(mBorderWidth/2));//Path 设置完成,恢复矩形
mPath.setFillType(Path.FillType.WINDING);
mIsPathDirty = false;
}
}
从上面的代码中可以大致了解到其主要是根据属性值 (mIsCircle) 来配置 Path,主要使用到 Path 的两个方法:addCircle() 和 addRoundRect(),这两个方法分别实现绘制圆形和绘制矩形