关于CircleImageView的使用很简单的,我就不说了,直接从源码开始分析,通过测试可以发现是从setImageXXX()开始呢,
/**
* 以下四个函数都是
* 复写ImageView的setImageXxx()方法
* 注意这个函数先于构造函数调用之前调用
* @param bm
*/
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
mBitmap = bm;
setup();
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
mBitmap = getBitmapFromDrawable(drawable);
System.out.println("setImageDrawable -- setup");
setup();
}
@Override
public void setImageResource(@DrawableRes int resId) {
super.setImageResource(resId);
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}
看上面代码会发现,四个函数都是获取图片Bitmap,调用setup()。
接下来那我们查看setup()源码:
//因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内//设置mSetupPending为true然后直接返回,后面的代码并没有执行。 if (!mReady) { mSetupPending = true; return; }
然后从CircleImageView()构造函数看起
public CircleImageView(Context context) { super(context); init();}public CircleImageView(Context context, AttributeSet attrs) { this(context, attrs, 0);}public CircleImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle);//通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) , 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0); mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH); mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR); mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY); mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);//调用 recycle() 回收TypedArray,以便后面重用 a.recycle(); init();}
获取自定义属性att.xml中得属性
<resources> <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></resources>
接着我们看init()方法
private void init() { super.setScaleType(SCALE_TYPE); mReady = true; //上边mSetupPending设置为true,所以这里执行setup() if (mSetupPending) { setup(); mSetupPending = false; } }
接着我们看看setup()
/** * 这个函数很关键,进行图片画笔边界画笔(Paint)一些重绘参数初始化: * 构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等, * 以及调用updateShaderMatrix()函数和 invalidate()函数; */private void setup() { //此时mReady为false,if不执行 if (!mReady) { mSetupPending = true; return; } if (getWidth() == 0 && getHeight() == 0) { return; } //防止空指针异常 if (mBitmap == null) { invalidate(); return; } // 构建渲染器,用mBitmap来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸 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(); // 设置含边界显示区域,取的是CircleImageView的布局实际大小,为方形 mBorderRect.set(calculateBounds()); //计算 圆形带边界部分(外圆)的最小半径 mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f); // 初始图片显示区域为mBorderRect(CircleImageView的布局实际大小) mDrawableRect.set(mBorderRect); if (!mBorderOverlay && mBorderWidth > 0) { mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f); } //这里计算的是内圆的最小半径,也即去除边界宽度的半径 mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f); applyColorFilter(); //设置渲染器的变换矩阵也即是mBitmap用何种缩放形式填充 updateShaderMatrix(); //手动触发ondraw()函数 完成最终的绘制 invalidate();}
上面代码注释我写的很详细不再一步步解释了,进行图片画笔边界画笔(Paint)一些重绘参数初始化:构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数。
这里关于半径的计算,我画图举个例子:CircleImageView的布局宽高度均为160,边界的宽度为10如图所示:
那么去除边界宽度的内圆半径为70,带边界部分的外圆半径为75
接下来我们看看updateShaderMatrix()函数,
/** * 这个函数为设置BitmapShader的Matrix参数,设置最小缩放比例,平移参数。 * 作用:保证图片损失度最小和始终绘制图片正中央的那部分 */private void updateShaderMatrix() { float scale; float dx = 0; float dy = 0; mShaderMatrix.set(null); //取最小的缩放比例 if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) { //y轴缩放 x轴平移 使得图片的y轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样) scale = mDrawableRect.height() / (float) mBitmapHeight; dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f; } else {//x轴缩放 y轴平移 使得图片的x轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样) scale = mDrawableRect.width() / (float) mBitmapWidth; dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f; } // shaeder的变换矩阵,我们这里主要用于放大或者缩小。 mShaderMatrix.setScale(scale, scale); // 平移 mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top); // 设置变换矩阵 mBitmapShader.setLocalMatrix(mShaderMatrix);}
通过updateShaderMatrix函数设置BitmapShader的Matrix参数,对图片mBitmap位置用缩放平移形式填充,目的是用最小的缩放比例,使得图片的某个方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样。做到了图片损失度最小。同时scale保证Bitmap的宽或高和目标区域一致,那么高或宽就需要进行位移,使得Bitmap居中。
现在万事俱备,只欠ondraw()了。接着上面再setup()最后会调用invalidate()函数触发ondraw()函数完成最终的绘制。查看源码
protected void onDraw(Canvas canvas) { if (mDisableCircularTransformation) { super.onDraw(canvas); return; } //如果图片不存在就不画 if (mBitmap == null) { return; } if (mFillColor != Color.TRANSPARENT) { canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint); } //绘制内圆形,参数内圆半径,图片画笔为mBitmapPaint canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint); if (mBorderWidth > 0) { //如果圆形边缘的宽度不为0 我们还要绘制带边界的外圆形 参数外圆半径,边界画笔为mBorderPaint canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint); } }
使用配置好的mBitmapPaint和mBorderPaint先画出绘制内圆形来以后再画边界圆形。源码还有一些自定义设置样式函数,很简单。
大功告成,是不是觉得思路比较简单,精致干练。
我总结下源码的精致之处:
流程控制的比较严谨,比如setup函数的使用
updateShaderMatrix保证图片损失度最小和始终绘制图片正中央的那部分
作者思路是画圆用渲染器位图填充,而不是把Bitmap重绘切割成一个圆形图片。
共同学习,写下你的评论
评论加载中...
作者其他优质文章