在项目开发中,我们经常需要用到圆形图片效果,典型案例是用户头像的显示。
如图所示。
下面我们使用开源控件CircleImageView来实现该效果。
CircleImageView项目下载地址:
https://github.com/hdodenhof/CircleImageView
(1).CircleImageView的使用
首先我们将CircleImageView添加到gradle。
civ_border_color: 设置边框的颜色,默认为黑色。
civ_border_overlay:设置边框是否覆盖在图片上,默认为false,即边框在图片外圈。
civ_fill_color: 设置图片的底色,默认透明。
接下来在布局文件中引入CircleImageView。
注意:CircleImageView的默认ScaleType为CENTER_CROP,且只能为CENTER_CROP。
(2).CircleImageView的源码分析
通过查看源码可以看到,内部核心方法主要是对Paint、Canvas、BitmapShader、Matrix、RectF类的使用。
我们分析一下代码的执行流程。首先可以通过布局文件中的src属性或者java代码的setImageXxx()方法来设置图片,控件在构造方法中获取到自定义属性的参数值,然后会执行到setup()这个方法。
上述setup()方法中使用到了updateShaderMatrix()方法。
该方法的作用是设置mBitmapShader变量的mShaderMatrix参数,对图片进行缩放setScale()和平移postTranslate(),使图片的显示区域缩放到与mDrawableRect一致,并通过平移确保显示图片的中心位置。
在onDraw()方法中,使用setup()中设置好的各项参数,完成图片、底色和边框的绘制。到这里,流程执行结束。
下面我们使用开源控件CircleImageView来实现该效果。
CircleImageView项目下载地址:
https://github.com/hdodenhof/CircleImageView
(1).CircleImageView的使用
首先我们将CircleImageView添加到gradle。
-
dependencies {
-
compile 'de.hdodenhof:circleimageview:2.1.0'
-
}
然后看一下自定义属性attrs:
-
<declare-styleable name="CircleImageView">
-
<attr name="civ_border_width" format="dimension" />
-
<attr name="civ_border_color" format="color" />
-
<attr name="civ_border_overlay" format="boolean" />
-
<attr name="civ_fill_color" format="color" />
-
</declare-styleable>
civ_border_color: 设置边框的颜色,默认为黑色。
civ_border_overlay:设置边框是否覆盖在图片上,默认为false,即边框在图片外圈。
civ_fill_color: 设置图片的底色,默认透明。
接下来在布局文件中引入CircleImageView。
-
<de.hdodenhof.circleimageview.CircleImageView
-
xmlns:circleimageview= "http://schemas.android.com/apk/res-auto"
-
android:id= "@+id/imageview"
-
android:layout_width= "wrap_content"
-
android:layout_height= "wrap_content"
-
android:src= "@drawable/profile"
-
circleimageview:civ_border_color= "@android:color/holo_red_light"
-
circleimageview:civ_border_overlay= "false"
-
circleimageview:civ_border_width= "2dp"
-
circleimageview:civ_fill_color= "@android:color/holo_blue_light"/>
注意:CircleImageView的默认ScaleType为CENTER_CROP,且只能为CENTER_CROP。
(2).CircleImageView的源码分析
通过查看源码可以看到,内部核心方法主要是对Paint、Canvas、BitmapShader、Matrix、RectF类的使用。
我们分析一下代码的执行流程。首先可以通过布局文件中的src属性或者java代码的setImageXxx()方法来设置图片,控件在构造方法中获取到自定义属性的参数值,然后会执行到setup()这个方法。
我们来分析一下setup()方法,该方法是CircleImageView的核心。
-
private void setup() {
-
// mReady和mSetupPending属性,在构造方法和setup()方法都有引用和赋值
-
// 目的是确保setup()内部逻辑的执行是在构造方法执行完毕之后,即获取到自定义属性参数之后
-
if (!mReady) {
-
mSetupPending = true;
-
return;
-
}
-
-
// 图片宽高为0,不往下执行
-
if (getWidth() == 0 && getHeight() == 0) {
-
return;
-
}
-
-
// 只有传入了图片,才会往下执行
-
if (mBitmap == null) {
-
invalidate();
-
return;
-
}
-
-
// BitmapShader类用来渲染头像
-
// 参数1:要处理的bitmap对象,参数2和3:指定模式为对图片的边缘进行拉伸
-
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
-
-
// 图片画笔:设置抗锯齿
-
mBitmapPaint.setAntiAlias( true);
-
// 图片画笔:设置渲染器
-
mBitmapPaint.setShader(mBitmapShader);
-
-
// 边框画笔:设置样式为描边
-
mBorderPaint.setStyle(Paint.Style.STROKE);
-
// 边框画笔:设置抗锯齿
-
mBorderPaint.setAntiAlias( true);
-
// 边框画笔:设置颜色值
-
mBorderPaint.setColor(mBorderColor);
-
// 边框画笔:设置宽度
-
mBorderPaint.setStrokeWidth(mBorderWidth);
-
-
// 底色画笔:设置样式为填充
-
mFillPaint.setStyle(Paint.Style.FILL);
-
// 底色画笔:设置抗锯齿
-
mFillPaint.setAntiAlias( true);
-
// 底色画笔:设置颜色
-
mFillPaint.setColor(mFillColor);
-
-
// 获取原图的高度
-
mBitmapHeight = mBitmap.getHeight();
-
// 获取原图的宽度
-
mBitmapWidth = mBitmap.getWidth();
-
-
// 设置边框的显示区域
-
mBorderRect.set(calculateBounds());
-
// 计算边框的半径
-
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
-
-
// 设置图片的显示区域为上面计算出来的mBorderRect
-
mDrawableRect.set(mBorderRect);
-
// 如果边框不是覆盖在图片之上,并且边框宽度大于0,扩大图片的显示区域,增加mBorderWidth-1
-
if (!mBorderOverlay && mBorderWidth > 0) {
-
mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
-
}
-
// 计算图片的半径
-
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
-
-
// 该方法内部只有一行代码,mBitmapPaint.setColorFilter(mColorFilter),给bitmap添加滤镜
-
applyColorFilter();
-
updateShaderMatrix(); // 下文会介绍
-
invalidate(); // 下文会介绍
-
}
上述setup()方法中使用到了updateShaderMatrix()方法。
-
private void updateShaderMatrix() {
-
float scale;
-
float dx = 0;
-
float dy = 0;
-
-
mShaderMatrix.set( null);
-
-
// 这里取最小的缩放比例,以尽量保证图片的质量
-
// 判断如果宽度的比例大于高度的比例,取高度的缩放比,平移x轴方向
-
// 否则取宽度的缩放比,平移y轴方向
-
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
-
scale = mDrawableRect.height() / ( float) mBitmapHeight;
-
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
-
} else {
-
scale = mDrawableRect.width() / ( float) mBitmapWidth;
-
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
-
}
-
-
// 设置mShaderMatrix:缩放
-
mShaderMatrix.setScale(scale, scale);
-
// 设置mShaderMatrix:平移
-
mShaderMatrix.postTranslate(( int) (dx + 0.5f) + mDrawableRect.left, ( int) (dy + 0.5f) + mDrawableRect.top);
-
-
// 将mShaderMatrix设置到mBitmapShader
-
mBitmapShader.setLocalMatrix(mShaderMatrix);
-
}
该方法的作用是设置mBitmapShader变量的mShaderMatrix参数,对图片进行缩放setScale()和平移postTranslate(),使图片的显示区域缩放到与mDrawableRect一致,并通过平移确保显示图片的中心位置。
在setup()方法中最后一行执行invalidate(),触发View的onDraw()方法。
-
-
protected void onDraw(Canvas canvas) {
-
// 如果禁止显示圆形(setDisableCircularTransformation()方法可设置),则不往下执行。
-
if (mDisableCircularTransformation) {
-
super.onDraw(canvas);
-
return;
-
}
-
-
// 未设置图片,不往下执行
-
if (mBitmap == null) {
-
return;
-
}
-
-
// 如果底色不是透明,绘制底色
-
if (mFillColor != Color.TRANSPARENT) {
-
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint);
-
}
-
// 使用上面设置好的mDrawableRect、mDrawableRadius、mBitmapPaint,绘制圆形图片,即内圆
-
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
-
// 如果边框宽度大于0,使用上面设置好的mBorderRect、mBorderRadius、mBorderPaint,绘制圆形边框,即外圆
-
if (mBorderWidth > 0) {
-
canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
-
}
-
}
在onDraw()方法中,使用setup()中设置好的各项参数,完成图片、底色和边框的绘制。到这里,流程执行结束。