作者: 林子木
博客地址:http://blog.csdn.net/wolinxuebin
文章目的
最近在看源码,发现网上有一些对ImageView的缩放模式有介绍的地方,但是对个别缩放模式说明还是存在一些问题,其次,就是缺乏一个总体的对比,也就是从他们的例子中无法分辨某几个模式的区别,尤其是几个Center模式。所以在看源码的时候,顺便写下这篇文章很大家分享。
文章主要内容
第一章直接进行一个各个模式比较的表格。第二章将具体将每个例子进行编写demo并且截图处理。第三章将从源码的角度分析每种模式的原理。
第一章 各个模式总结表
表1-1 ImageView 各个ScaleType说明表
缩放模式
裁剪
按比例
放大
缩小
描述
MATIRX
未知
未知
未知
未知
通过设定setImageMatrix函数相应的矩阵,来完成。
FIT_XY
否
否
是
是
直接将图片铺满整个view
CENTER
是
是
否
否
1) 如果图片较小,直接居中全部显示
2) 如果图片较大,裁剪后居中显示
CENTER_CROP
是
是
是
是
按比例缩放,按照缩放比例大的进行缩放,然后居中显示(除非图片和view的形状一样,
否则一定存在裁剪,所以有的描述为图片的长宽≥View的长宽)
CENTER_INSIDE
否
是
否
是
1) 如果图片较小,直接放入view,不进行放大处理,居中显示
2) 将图片按比例进行缩放,使得图片完全展示,并且长或者宽等于view的长和宽
(所以有的描述为图片的长宽≤View的长宽)
FIT_START
否
是
是
是
对图片的处理和FIT_CENTER,区别为:
1) view的横向有空,居左边
2) view的纵向有空,居上边
FIT_CENTER(默认)
否
是
是
是
将图片按比例进行缩放,使得图片完全展示,并且长或者宽等于view的长和宽
(所以有的描述为图片的长宽≤View的长宽),最后居中显示
FIT_END
否
是
是
是
对图片的处理和FIT_CENTER,区别为:
1) view的横向有空,居右边
2) view的纵向有空,居下边
通过以上的表格大家应该能对各个模式有一个大致的了解,但是大家肯定有一个疑惑,就是对CENTER、CENTER_CROP、CENTER_INSIDE以及FIT_CENTER有点分布清的感觉,那么让我们再来阅读下面一个表格,来更好的认清。
表1-2 各个Center缩放模式的比较
缩放模式
图片比View小
dH <= vH && dW <= vW
图片比View大
dH > vH || dW > vW
是否裁剪
CENTER
直接居中显示
直接截取
图片比View大的时候存在裁剪
CENTER_CROP
按比例缩放,最后
dH >= vH 或 dw >= vW
同左
始终存在(除非图片和view的形状一样)
CENTER_INSIDE
直接居中显示
按比例缩放,最后
dH <= vH 或 dw <= vW
不裁剪
FIT_CENTER
始终按比例缩放,最后
dH <= vH 或 dw <= vW
同左
不裁剪
注:1)dH dw 代表图片的高和宽,vH vW代表View的高和宽
2)ImageView的模式缩放模式是Fit_Center
第二章、各个缩放模式的示例
原图如下:
图 2.1 示例原图(中间的十字叉)
2.1 MATIRX
这个需要设置相应的矩阵,所以什么情况都有,所以先不做相应的示例。
2.2 FIT_XY
直接将图片不按比例铺满View,所以就可能图片就扭曲了,如下图:
2.2 FIT_XY 模式示例图
2.3 CENTER模式
该模式,在图片较小的情况下,直接居中显示(如图2.3 左);如果图片比view大,那么直接裁剪后居中显示(如图2.3 右),如下图:
图2.3 Center模式, 左边为图片比View小, 右边为图片比View大
2.4 CENTER_CROP
该模式一律将图片放大,并且使得图片的宽高≥View的宽高,所以如果图片和View的形状不一样(也就是宽和高的缩放比例不一致),将存在图片的裁剪,如下图:
图2.4 CENTER_CROP模式,
取View宽/图宽,View高/图高大的比例对宽高进行缩放,出现了截断的效果。
2.5 CENTER_INSIDE
如果图片小的时候,不进行缩放,直接居中显示(如图2.5 左);如果图片较大,那么将进行按比例进行缩小处理,最后图片的宽高≤View的宽高(如图2.5 右)。
图2.5 Center_Inside模式图,左图为图片较View小,右图为图片较View大(横向)
2.6 FIT_CENTER
始终进行缩放,最后使得图片的宽高≤View的宽高,如图2.6所示。
图 2.6 FIT_CENTER 模式, 左图为按纵向缩放,右图为按横向进行缩放
2.7 FIT_START
缩放模式和FIT_CENTER一样,唯一的却别是图片居于View的位置。原则是,缩放有,有空白的方向的左边(如图2.7 左)或上边(如图2.7 右),因为按照其中一个方向进行缩放,那么该方向上图片将和View一致,另一个方向上留有空白。注,这里是LTR模式,如果是RTL将左边和右边相反。
图 2.7 FIT_START模式,左边为按照纵向缩放,右边为按照横向缩放。
2.8 FIT_END
该模式和FIT_START一样,唯一的区别是,为居右(如图 2.8 左)和 居下(如图 2.8 右)。
图 2.8 FIT_END 模式,左边为按照纵向缩放,右边为按照横向缩放。
第三章 从源码角度看各个缩放模式
该源码是在ImageView的configureBounds()函数中体现,具体如下:
首先获取相应图片和View的宽高
int dwidth = mDrawableWidth;
int dheight = mDrawableHeight;
//[lxb #] 获取当前view的宽高
int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
int vheight = getHeight() - mPaddingTop - mPaddingBottom;
接着进行fits变量标记,这个fits为true则使用FIT_XY模式:
//[lxb #] dwidth < 0 表示,继承了Drawable的view没有重写getTrinsicWidth则返回-1,表示view没有原始大小的说明。
boolean fits = (dwidth < 0 || vwidth == dwidth) &&
(dheight < 0 || vheight == dheight);
2.1 FIT_XY
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
/* If the drawable has no intrinsic size, or we're told to
scaletofit, then we just fill our entire view.
*/
//[lxb #] 采用当前view的宽高
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
}
2.2 MATRIX
//[lxb #] MATRIX模式,是采用设置矩阵的方式进行变换
if (ScaleType.MATRIX == mScaleType) {
// Use the specified matrix as-is.
//[lxb #] 是不是单位矩阵,单位矩阵的话,就不需要进行变换,就是原始图
if (mMatrix.isIdentity()) {
mDrawMatrix = null;
} else {
mDrawMatrix = mMatrix;
}
}
2.3 CENTER
else if (ScaleType.CENTER == mScaleType) {
// Center bitmap in view, no scaling.
//[lxb #] CENTER 模式
//[lxb #] mMatrix 如果没有调用setImageMatrix函数,mMatrix初始化为一个单位矩阵
//[lxb #] CENTER 就是将drawable进行平移,将drawble的中心点和view的中心点对齐
mDrawMatrix = mMatrix;
mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
Math.round((vheight - dheight) * 0.5f));
}
2.4 CENTER_CROP
else if (ScaleType.CENTER_CROP == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
float dx = 0, dy = 0;
//[lxb #] 化简如下 vh/dh > vw/dw,也就是要将drawable放入到view中,按照需要进行大缩放的为准
//[lxb #] 说人话就是,将drawable按比例缩放到view的大小,使得长宽都大于等于view的长宽(其中一个相等),并居中显示
//[lxb #] 所以如果drawable和view的长宽比不一样的话,就会被切割
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));
}
2.5 CENTER_INSIDE
else if (ScaleType.CENTER_INSIDE == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
float dx;
float dy;
//[lxb #] 简单的理解,如果drawble能放到view中,那么直接塞进去,否则按比例缩小放进去
//[lxb #] 和fitCenter的区别是,如果当前的图片比view小,就直接放进行,不缩放
//[lxb #] 而fitCentter则是进行缩放,使得宽或者长等于view的宽或者长
if (dwidth <= vwidth && dheight <= vheight) {
scale = 1.0f;
} else {
scale = Math.min((float) vwidth / (float) dwidth,
(float) vheight / (float) dheight);
}
//[lxb #] 按比例缩小并居中对齐
dx = Math.round((vwidth - dwidth * scale) * 0.5f);
dy = Math.round((vheight - dheight * scale) * 0.5f);
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(dx, dy);
}
2.7 FIT_START, FIT_CENTER, FIT_END
先执行如下代码:
else {
// Generate the required transform.
//[lxb #] FIT_START(2) FIT_END(4) FIT_CENTER(3)
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);
mDrawMatrix = mMatrix;
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
}
最后走Matrix_Delegate.java 的 native_setRectToRect函数,如下:
@LayoutlibDelegate
/*package*/ static boolean native_setRectToRect(long native_object, RectF src,
RectF dst, int stf) {
Matrix_Delegate d = sManager.getDelegate(native_object);
if (d == null) {
return false;
}
if (src.isEmpty()) {
reset(d.mValues);
return false;
}
if (dst.isEmpty()) {
d.mValues[0] = d.mValues[1] = d.mValues[2] = d.mValues[3] = d.mValues[4] = d.mValues[5]
= d.mValues[6] = d.mValues[7] = 0;
d.mValues[8] = 1;
} else {
//[lxb #] sx, sy 表示x轴, y轴的缩放
//[lxb #] tx, ty 表示x轴,y轴的平移
float tx, sx = dst.width() / src.width();
float ty, sy = dst.height() / src.height();
boolean xLarger = false;
if (stf != ScaleToFit.FILL.nativeInt) {
//[lxb #] 取小的比率,经过下面的处理,选择了较小的放大率,sx == sy,简单的理解就是,
//[lxb #] 使得图片按照比例放大缩小,使得长宽小于等于view的长宽
if (sx > sy) {
//[lxb #] 标记X为比较大的
xLarger = true;
sx = sy;
} else {
sy = sx;
}
}
//[lxb #] 以原点为基准点,如果按比例缩放,那么每一段距离都将按比例缩放
//[lxb #] Top
//[lxb #] Left --------------------------
//[lxb #] | | src.top
//[lxb #] |-----.(0,0)
//[lxb #] | src.left
//[lxb #] |
//[lxb #] |
//[lxb #] |
//[lxb #] |
//[lxb #] |
tx = dst.left - src.left * sx;
ty = dst.top - src.top * sy;
if (stf == ScaleToFit.CENTER.nativeInt || stf == ScaleToFit.END.nativeInt) {
float diff;
//[lxb #] diff 代表需要偏移的值,由上面可知,如果是CENTER,END,START sx == sy
//[lxb #] diff 最终代表的是,drawable/view的长和宽分别的比值大的一方的偏移量(为啥是大的?因为小的一方已经完全贴合了)
//[lxb #] 总结,最简单的理解是,有空隙的方向,没有铺满的方向的偏移量
if (xLarger) {
diff = dst.width() - src.width() * sy;
} else {
diff = dst.height() - src.height() * sy;
}
//[lxb #] 居中显示
if (stf == ScaleToFit.CENTER.nativeInt) {
diff = diff / 2;
}
//[lxb #] 如果你有疑问,为什么将大的进行偏移
if (xLarger) {
tx += diff;
} else {
ty += diff;
}
}
d.mValues[0] = sx;
d.mValues[4] = sy;
d.mValues[2] = tx;
d.mValues[5] = ty;
d.mValues[1] = d.mValues[3] = d.mValues[6] = d.mValues[7] = 0;
}
// shared cleanup
d.mValues[8] = 1;
return true;
}