Android Canvas Matrix —— 从CircleImageView说起

前段时间的一个项目中,用到了CircleImageView这个库(View in Github),因为项目比较近,虽然源码不多,但是也没仔细看,用轮子嘛。这两天有点时间,仔细看看这个精致的库,也多了解了不少和Canvas、Matrix相关的东西。

先贴一张CircleImageView的效果图:

circleimageview_preview

简单的说,这个CircleImageView的工作,就是把ImageView中的图像使用圆形的效果显示出来(,再加上一个描边)。并且图片的ScaleType被强制设定为CenterCrop,这一点比较好理解,就是将图片水平垂直居中,显示中间的一部分,如果图片过小,就放大后显示中间的一部分。

然后说一下这个库的核心原理和流程

  1. 通过复写ImageView的setImageXxx();方法,获得Bitmap。
  2. 使用BitmapShader,配置Paint画笔为位图样式(这个不好理解的话,可以想象以下美图秀秀的马赛克功能,就是把纹理画到另一张图片上)。
  3. 计算缩放比例,设置BitmapShader的Matrix参数。
  4. 使用配置好的Paint,直接画圆。
    有了这个流程,下面分析一下源码:

首先是Shader的使用

mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mBitmapPaint.setShader(mBitmapShader);

BitmapShader是Shader的子类,通过构造函数将Bitmap传入,并设置TileMode,然后再将Shader传到Paint中就行了。这里的TileMode对Bitmap并没有实际作用,后文会说到Bitmap实际上会保证填满绘图区域。TileMode的作用是为了如果ImageView的Source是颜色的图片下,Shader会保证将颜色铺满绘图区域。

其次是Matrix的设置

// mDrawableRect 这是绘图的区域
// 下面两个参数是为了保证图片居中显示
// dx 这是变换后在x轴方向所需的偏移量
// dy 这是变换后在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) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth);
mBitmapShader.setLocalMatrix(mShaderMatrix);

这段代码我看了很久才看明白,主要是开始的时候if语句的表达式没看明白,实际上这个布尔表达式应该按如下方式表示:

(bw / dw) > (bh / dh)

其中bw = mBitmapWidth; dw = mDrawableRect.width(); bh, bw 含义类似
要达到的目的是:用最小的缩放比例,使得图片的某个方向的边的尺寸和目标区域匹配(将图片的某个边缩放到目标区域一样)

再解释以下,如果Bitmap和Rect的宽的差距,相比于高的差距要小,那么就按宽的比例缩放。这样的缩放结果是,如果图片和目标区域的比例不一样,那么图片一定会比目标区域大!!!这也是上文中提到的TileMode不会有作用的原因。

还有一个细节是,计算scale和dx/dy的边是不同的,也是说明了,scale保证Bitmap的宽或高和目标区域一致,那么高或宽一定大于目标区域,所以高或宽就需要进行位移,使得Bitmap居中。下图显示了多种情况下进行缩放和偏移的结果以及位移的情况:

shader

注意dx是向量,也就是有方向的,图中情况,dx为负值。

这两个方面了解了之后,应该就可以完全理解这个库的做法了。不同于网上的很多将Bitmap重绘裁切成一个圆形的Bitmap,这个库的作者使用Shader达到了相同的目的,并且可以适应ImageView的src是颜色的情况。很佩服作者的思路:将图片画到一个圆里(而不是画一个圆形的图片)

这篇只是开了一个头,Canvas和Matrix,下一篇继续。 ^.^