为了账号安全,请及时绑定邮箱和手机立即绑定

圆形头像控件CircleImageView源码浅析

标签:
Android

关于CircleImageView的使用很简单的,我就不说了,直接从源码开始分析,通过测试可以发现是从setImageXXX()开始呢,

  1. /**

  2.      * 以下四个函数都是

  3.      * 复写ImageView的setImageXxx()方法 

  4.      * 注意这个函数先于构造函数调用之前调用

  5.      * @param bm

  6.      */

  7.     @Override

  8.     public void setImageBitmap(Bitmap bm) {

  9.         super.setImageBitmap(bm);

  10.         mBitmap = bm;

  11.         setup();

  12.     }

  13.  

  14.     @Override

  15.     public void setImageDrawable(Drawable drawable) {

  16.         super.setImageDrawable(drawable);

  17.         mBitmap = getBitmapFromDrawable(drawable);

  18.         System.out.println("setImageDrawable -- setup");  

  19.         setup();

  20.     }

  21.  

  22.     @Override

  23.     public void setImageResource(@DrawableRes int resId) {

  24.         super.setImageResource(resId);

  25.         mBitmap = getBitmapFromDrawable(getDrawable());

  26.         setup();

  27.     }

  28.  

  29.     @Override

  30.     public void setImageURI(Uri uri) {

  31.         super.setImageURI(uri);

  32.         mBitmap = getBitmapFromDrawable(getDrawable());

  33.         setup();

  34.     }

看上面代码会发现,四个函数都是获取图片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重绘切割成一个圆形图片。

原文链接:http://www.apkbus.com/blog-896840-63545.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消