原文发布于vcmo博客 by Jie
打开ImageView的源码,从源码入手ScaleType的各个属性值:
public enum ScaleType {
MATRIX (0),
FIT_XY (1),
FIT_START (2),
FIT_CENTER (3),
FIT_END (4),
CENTER (5),
CENTER_CROP (6),
CENTER_INSIDE (7);
ScaleType(int ni) {
nativeInt = ni;
}
final int nativeInt;
}
可以发现ScaleType是ImageView中定义的一个枚举类型。有上面的那些属性可以设置。当然源码中是包含英文注释的,对于这里每个枚举对象的作用和效果,网络上已经有很多前辈解释过,在此就不在赘述。我们从源码来看看这些属性是如何作用于ImageView的src定义的Drawable的。
上一篇博文源码分析Android 中ImageView的设置src与background绘制流程简单的介绍了ImageView的绘制流程,我们知道通过src设置的资源最终都会被解析为Drawable对象赋值与全局变量mDrawable。并且设置mDrawable的属性调用到configureBounds()这个方法,回到源代码:
private void configureBounds() {
···
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
} else {
mDrawable.setBounds(0, 0, dwidth, dheight);
if (ScaleType.MATRIX == mScaleType) {
if (mMatrix.isIdentity()) {
mDrawMatrix = null;
} else {
mDrawMatrix = mMatrix;
}
} else if (fits) {
mDrawMatrix = null;
} else if (ScaleType.CENTER == mScaleType) {
mDrawMatrix = mMatrix;
mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
Math.round((vheight - dheight) * 0.5f));
} else if (ScaleType.CENTER_CROP == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
float dx = 0, dy = 0;
if (dwidth * vheight > vwidth * dheight) {
scale = (float) vheight / (float) dheight;
dx = (vwidth - dwidth * scale) * 0.5f;
} else {
scale = (float) vwidth / (float) dwidth;
dy = (vheight - dheight * scale) * 0.5f;
}
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
} else if (ScaleType.CENTER_INSIDE == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
float dx;
float dy;
if (dwidth <= vwidth && dheight <= vheight) {
scale = 1.0f;
} else {
scale = Math.min((float) vwidth / (float) dwidth,
(float) vheight / (float) dheight);
}
dx = Math.round((vwidth - dwidth * scale) * 0.5f);
dy = Math.round((vheight - dheight * scale) * 0.5f);
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(dx, dy);
} else {
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);
mDrawMatrix = mMatrix;
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
}
}
}
在initImageView()方法中将mScaleType的默认值设为了ScaleType.FIT_CENTER。
我们接着看这个方法:
-
判断dwidth与dheight(从mDrawable的获得的宽高)有没有意义,或者mScaleType的值是否为ScaleType.FIT_XY,OK 其中一个,其调用的代码为:
mDrawable.setBounds(0, 0, vwidth, vheight); mDrawMatrix = null;
即按照ImageView内部空间大小设置mDrawable的大小,所以ScaleType.FIT_XY的效果 是伸缩至恰好填充控件。
-
进入另一个大分支,首先将mDrawable的边界范围设定为它自己的宽高,接着往后看:ScaleType.MATRIX == mScaleType
mDrawable.setBounds(0, 0, dwidth, dheight); if (ScaleType.MATRIX == mScaleType) { if (mMatrix.isIdentity()) { mDrawMatrix = null; } else { mDrawMatrix = mMatrix; } }
判断mMatrix是否为单位矩阵(对角线全一)来设置mDrawMatrix(将来在绘制的时候根据此矩阵来改变drawable)为null或者为mMatrix;而mMatrix默认是没有做任何变换的,只有在setImageMatrix()方法中改变其属性,由此得知mMatrix是用来给开发者通过矩阵变换drawable用的。所以可知ScaleType.MATRIX按照用户设置的Matrix变换,否则不会变换,即按照资源自身大小绘制。
-
下一个的是**ScaleType.CENTER **:
else if (ScaleType.CENTER == mScaleType) { mDrawMatrix = mMatrix; mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),Math.round((vheight - dheight) * 0.5f)); }
将mMatrix赋值与mDrawMatrix,此时还没有做任何变换。下一行代码mDrawMatrix发生了转换,setTranslate(float dx,float dy)方法作用是位移,传入的参数也很简单,就是ImageView的内部宽高与drawable宽高差的一半,即移动到ImageView的中间。
-
接下来是 ScaleType.CENTER_CROP,直接看源码:
else if (ScaleType.CENTER_CROP == mScaleType) { mDrawMatrix = mMatrix; float scale; float dx = 0, dy = 0; if (dwidth * vheight > vwidth * dheight) { scale = (float) vheight / (float) dheight; dx = (vwidth - dwidth * scale) * 0.5f; } else { scale = (float) vwidth / (float) dwidth; dy = (vheight - dheight * scale) * 0.5f; } mDrawMatrix.setScale(scale, scale); mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy)); }
可以看到,这个方法定义了一个scale缩放比例,dx,dy分别是x和y轴的偏移量,但下面的代码中dx和dy只会计算一个值。重点在中间的if (dwidth * vheight > vwidth * dheight)语句,其实我们可以将这个表达式变换一下,if(dwidth/vwidth > dheight/vheight)。一目了然,是比较x轴和y轴方向哪个比值更大,我们先假设这个条件成立,看下里会干什么,计算y轴的比例,然后求出dx,另一个分支则是相反计算出x轴的比例,求出dy,最后两句,先缩放图片,再设置偏移。我们回头看一下if语句,当进入第一个分支,scale 是根据y轴的比例计算的,所以此种情况图片的y轴就会缩放到与控件大小一致,y轴就不需要移动,所以只需要计算dx的值。同理另一种情况只需要计算dy的值,就可以让缩放后的图片居中了。现在来分析一下if的条件,图片与控件的大小,无非只有四类情况(临界状态随意上下归并):
- dwidth < vwidth && dheight < vheight
- dwidth > vwidth && dheight < vheight
- dwidth > vwidth && dheight > vheight
- dwidth < vwidth && dheight > vheight
具体的分析也很简单,我们最后可以得出结论ScaleType.CENTER_CROP会将图片尽量放大或者避免缩小。最终的结果都是宽高一个属性与控件的相等,另一属性值大于等于控件的值,会完全填充ImageView。且居中。
-
下一个ScaleType.CENTER_INSIDE:
else if (ScaleType.CENTER_INSIDE == mScaleType) { mDrawMatrix = mMatrix; float scale; float dx; float dy; if (dwidth <= vwidth && dheight <= vheight) { scale = 1.0f; } else { scale = Math.min((float) vwidth / (float) dwidth, (float) vheight / (float) dheight); } dx = Math.round((vwidth - dwidth * scale) * 0.5f); dy = Math.round((vheight - dheight * scale) * 0.5f); mDrawMatrix.setScale(scale, scale); mDrawMatrix.postTranslate(dx, dy); }
分析完了上一个这个就相对容易了。首先判断如果图片的宽高都小与控件的宽高,就不缩放。否则就分别判断x轴,y轴的比值,取较小值。其实大家可以想一下,既然第一种情况已经判断了图片宽高都小于控件的宽高了,那么现在一定会有一个方向图片是超出控件的,所控件/图片,一定至少有一个值小于1,而又是取两个的较小值,所以一定会是缩小,而且是尽量缩小(因为取得是最小的比例且小于1)。后面就是计算偏移量让图片居中了。所以ScaleType.CENTER_INSIDE特性我们可以总结:
- 图片宽高都小于控件,不缩放,居中
- 图片宽高只要有一方大于控件的。就尽量缩小,图片会完全显示在控件内,居中。
-
终于到了最后一个分支:
else { mTempSrc.set(0, 0, dwidth, dheight); mTempDst.set(0, 0, vwidth, vheight); mDrawMatrix = mMatrix; mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType)); }
到这里就分支完了,我们看ScaleType中还有三个没有提到:
- FIT_START
- FIT_CENTER
- FIT_END
在最后一个分支中可以看到调用的mDrawMatrix.setRectToRect()方法第三个参数是ScaleToFit类型,顿时明白了,FIT_*都在此了。去看下这个方法:
代码特别简单,就是根据mScaleType枚举类型的值去数组sS2FArray里面拿到对应的类型的值。sS2FArray[st.nativeInt - 1],ScaleType.FIT_START.nativeInt的值是2,2-1正好是Matrix.ScaleToFit.START在数组中的位置,其它也同样。所以这三个FIT_都是通过 mDrawMatrix.setRectToRect()方法作用于矩阵来改变视图的。这三种效果都特别简单,与FIT_XY不同的是,它们不会在两个方向上对图片拉伸,会 保持图片比例,高度与控件内部一致,三个属性分别对应开始、中间、末尾(一般是左、中、右,是按照文字阅读方向)三个位置。**
我们已经获得了变换后的矩阵mDrawMatrix,最终在onDraw()方法中使用:
至此,我们的源码分析已经结束了。
最后一个小疑惑:
在sS2FArray数组中有Matrix.ScaleToFit.FILL值,想必就是与FIT_XY对应的(按照ScaleType.FIT_XY.nativeInt值=1,1=1是Matrix.ScaleToFit.FILL在数组中的位置),但是在上面已经对mScaleType=ScaleType.FIT_XY做过处理了,不会到最后一个分支,也就是说数组中Matrix.ScaleToFit.FILL永远不会被用上了?如有理解的朋友还望告知。
欢迎分享交流
邮箱:[email protected]
原创博文 转载请注明出处。