CircleImageView 的工作原理

阅读 482
收藏 0
2017-06-02
原文链接: www.jianshu.com

CircleImageView的工作原理

项目地址:CircleImageView

在分析CircleImageView源码之前,先学习一些知识点

知识点1:BitmapShader

BitmapShader 继承自Shader,而Shader有5个子类:

  • BitmapShader
  • ComposeShader
  • LinearGradient
  • RadialGradient
  • SweepGradient

首先来认识一下Shader是什么?

直接翻译过来叫着色器,没了解过的人看了会感觉很蒙,就跟把Socket翻译成套接字似的,现实生活在跟本没有这玩意啊,怎么理解呢?

经过我细致的揣摩,我认为Shader就像是一把刷子或者说是印章,它决定写出来的东西是什么颜色的,或者画出到图案是什么样子到。

比如说我想要制作一台车,在所有都组装完毕之后,加了油就可以开了,但是看起来怎么这么不狂拽炫吊炸天呢? 哦对了,忘了给车喷漆了,这时候我们就拿几喷漆罐对着车喷一些文字或图案,那喷图案的过程就是Shader的过程.

我们上面提到有5种Shader:

  • BitmapShader ------ 图案喷漆
  • ComposeShader ------ 将其他喷漆合并
  • LinearGradient ------ 线性渐变色的喷漆
  • RadialGradient ------ 环形渐变色的喷漆
  • SweepGradient ------ 扫描喷漆(类似于从某一个点射出很多不同颜色的射线)

那么我们重点关注一下BitmapShader,它是一个可以将图案绘制出来的喷漆,比如说我要把小狗的图像作为一个喷漆,那么我拿着这个喷漆对着车喷一下,车马上就会有一只可爱的小狗的图画.

知识点2:ColorFilter

这个通过直译就比较好理解,颜色过滤器,就像是一个筛子,比如说我有3种类型的物体:三角形,矩形,圆形,让他们通过管道从一个地方传递到另外一个地方,如果管道是完全通透的,那么,这些物体从起始点到终点都是一样的,那么如果我中间加一个三角形过滤器,只有三角形能通过,那么剩下矩形和圆形就过不去了,颜色过滤器类似,但是它能设置量,既通过的多少.

ColorFilter在Android中是一个5* 4的矩阵:

        {
            1, 0, 0, 0, 0    // 颜色种的红色R
            0, 1, 0, 0, 0    // 颜色种的绿色G
            0, 0, 1, 0, 0    // 颜色种的蓝色B
            0, 0, 0, 1, 0    // 颜色种的透明度A
        }

如上所示,其中1的位置就是每个颜色的过滤器,范围0 ~ 2,其中1表示正常颜色,0表示完全隔断,2表示FF,所以我们如果要调整一张图片的颜色,只要调整ColorFilter种的值就可以做到了.

知识点3:Matrix

这里讨论的是Android中android.graphics.Matrix这个类

Android种的Martix是用来做图像调整的,如平移,缩放,旋转,还有错切,它是一个3 * 3的矩阵.

{
缩放X, 错切X, 平移X,
错切Y, 缩放Y,  平移Y,
透视1, 透视2,  透视3,
}

其中透视基本不用,咱主要用到的是平移和缩放。


正式开始

public class CircleImageView extends ImageView {
    //首先强制设置缩放类型为CENTER_CROP
    private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;

    //其次,Bitmap类型为ARGB_8888
    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
    private static final int COLORDRAWABLE_DIMENSION = 2;

    private static final int DEFAULT_BORDER_WIDTH = 0;
    private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
    private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT;
    private static final boolean DEFAULT_BORDER_OVERLAY = false;

然后在UI在创建时会执行init();

    private void init() {
        //设置ScaleType
        super.setScaleType(SCALE_TYPE);
        mReady = true;

        if (mSetupPending) {
            //重点看一下setup
            setup();
            mSetupPending = false;
        }
    }

我们重点关注一下setup()

    private void setup() {
        ...

        //初始化一个BitmapShader,就是上面讲的会喷图的喷漆
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        //将喷图赋予mBitmapPaint这个画笔,感觉上面的比喻有点不对,BitmapShader应该只是一个图案,喷漆应该是Paint
        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);

        //取到bitmap的宽高信息
        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();

        //计算圆形头像的范围,如果宽 > 高,则用高作为正方形边长
        mBorderRect.set(calculateBounds());
        //半径为宽或者高的一半
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
        //DrawableRect为里面图案的范围,而BorderRect则是边框的范围
        mDrawableRect.set(mBorderRect);
        if (!mBorderOverlay && mBorderWidth > 0) {
            //缩小mBorderWidth - 1 个像素
            mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
        }
        mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);

        //设置颜色过滤器
        applyColorFilter();
        //更新Matrix
        updateShaderMatrix();
        //刷新UI
        invalidate();
    }

接下来再看看updateShaderMatrix

    private void updateShaderMatrix() {
        float scale;
        float dx = 0;
        float dy = 0;
        //reset the matrix
        mShaderMatrix.set(null);
        //计算从原始的bitmap到UI需要显示的bitmap,需要的缩放和位移,其中,如果原图宽度大于高度,则只位移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.setScale(scale, scale);
        //传入位移设置
        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
        //将Martix设置给BitmapShader
        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

所以,通过查看源码我们就能了解到,CircleImageView的工作原理是将ImageView的Bitmap捕获到,然后将这个Bitmap设置给BitmapShader,其中利用Matrix调整Bitmap在BitmapShader中的大小为ImageView大小并且位置居中,然后把BitmapShader利用Paint.setShader赋值给画笔Paint,最后再利用这个画笔绘制一个圆形即可画出我们设置的图片,而且是圆形,所以利用这个原理,我们可以画任何我们想要的形状哦。