ScaleType作为一个枚举类型,一共定义了八个模式
八个ScaleType,其实可以分为三个类型:
以FIT_开头的4种,它们的共同点是都会对图片进行缩放。
以CENTER_开头的三种,它们的共同点是居中显示,图片的中心点会与ImageView的中心点重叠。
MATRIX,不改变原图的大小,从ImageView的左上角开始绘制,超出的位置做剪切处理;原图小于ImageView则只绘制原图大小,其他地方空白。
FIT_START(保持原图片宽高比例)
把原图按照比例放大或缩小,显示在ImageView的start(左部/上部),具体是怎么缩放,和图片的宽高有关系。
当图片宽度大于高度时,按照宽度放大/缩小,图片宽度放大/缩小到ImageView的宽度,高度按同样比例放大/缩小,显示在ImageView的上半部分。
当图片高度大于宽度时,按照高度放大/缩小,图片高度放大/缩小到ImageView的高度,宽度按同样比例放大/缩小,显示在ImageView的左半部分。
FIT_END(保持原图片宽高比例)
FIT_END和FIT_START类似,FIT_START是左/上部,FIT_END是右/下部。
FIT_CENTER(默认模式,保持原图片宽高比例)
把原图按照比例放大或缩小,显示在ImageView的center(中部),具体是怎么缩放,和图片的宽高有关系
当图片宽度大于高度时,按照宽度放大/缩小,图片宽度放大/缩小到ImageView的宽度,高度按同样比例放大/缩小,显示在ImageView的中间。
当图片高度大于宽度时,按照高度放大/缩小,图片高度放大/缩小到ImageView的高度,宽度按同样比例放大/缩小,显示在ImageView的中间。
把图片放大/缩小到ImageView的大小,不保持原图片比例,简单粗暴。
对于FIT_START,FIT_END,FIT_CENTER来说,图片的宽和高哪个更接近ImageView对应的宽和高,哪个(宽/高)就先缩放到ImageView的大小,然后另一个按前面的比例进行缩放,会保持图片的原宽高比例,而且一定会显示完整的图片,就是图片的所有部分,不会造成放大或缩小后只显示图片的一部分内容;FIX_XT就是简单粗暴的缩放到ImageView的大小,不会保持图片的原宽高比例,但是也会显示图片的所有内容。
所以FIT_开头的方式都能显示图片的所有内容。
PS:显示图片的所有内容意思就是,把图片上所有东西都显示出来,有些放大/缩小只是放大/缩小一部分内容,比如放大上面那个图片的打电脑的女生,然后放大到充满ImageView,但是我们就看不到LeetCode这段文字了,这样就不能显示图片的所有内容。
CENTER_CROP
本来以为缩放也会和宽高有关,但是看到下面这个图就猜不出具体是什么关系了,只能在后面的源码分析里面看一下是怎么缩放的。
CENTER_INSIDE(保持原图片宽高比例)
以原图正常显示为目的,如果原图大小大于ImageView的size,就按照比例缩小原图的宽高,直到较长的一端刚好能显示在ImageView中,居中显示在ImageView中。如果原图size小于ImageView的size,则不做处理居中显示图片。如下图所示:
CENTER
保持原图的大小,显示在ImageView的中心。当原图的size大于ImageView的size时,多出来的部分被截掉。
不改变原图的大小,从ImageView的左上角开始绘制,超出的位置做剪切处理。
Matrix (0), //Scale the image using {@link Matrix.ScaleToFit#FILL}. FIT_XY (1), //Scale the image using {@link Matrix.ScaleToFit#START}. FIT_START (2), //Scale the image using {@link Matrix.ScaleToFit#START}. FIT_CENTER (3), //Scale the image using {@link Matrix.ScaleToFit#CENTER}. FIT_END (4), //Scale the image using {@link Matrix.ScaleToFit#END}. CENTER (5), //Center the image in the view, but perform no scaling. CENTER_CROP (6), /** * Scale the image uniformly (maintain the image's aspect ratio) so * that both dimensions (width and height) of the image will be equal * to or larger than the corresponding dimension of the view * (minus padding). The image is then centered in the view. */ CENTER_INSIDE (7) /** * Scale the image uniformly (maintain the image's aspect ratio) so * that both dimensions (width and height) of the image will be equal * to or less than the corresponding dimension of the view * (minus padding). The image is then centered in the view. */ 复制代码
设置完ScaleType之后,具体的实现细节在ImageView的configureBounds()方法中(android-29)
private void configureBounds() { if (mDrawable == null || !mHaveFrame) { return; } final int dwidth = mDrawableWidth; //图片宽度 final int dheight = mDrawableHeight;//图片高度 final int vwidth = getWidth() - mPaddingLeft - mPaddingRight; //ImageView宽度 final int vheight = getHeight() - mPaddingTop - mPaddingBottom;//ImageView高度 final boolean fits = (dwidth < 0 || vwidth == dwidth) && (dheight < 0 || vheight == dheight); if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) { //FIX_XY /* If the drawable has no intrinsic size, or we're told to scaletofit, then we just fill our entire view. */ mDrawable.setBounds(0, 0, vwidth, vheight); //FIT_XY最简单粗暴 mDrawMatrix = null; } else { // We need to do the scaling ourself, so have the drawable // use its native size. mDrawable.setBounds(0, 0, dwidth, dheight); if (ScaleType.MATRIX == mScaleType) { // Use the specified matrix as-is. if (mMatrix.isIdentity()) { mDrawMatrix = null; } else { mDrawMatrix = mMatrix; } } else if (fits) { //图片宽高与ImageView一致 // The bitmap fits exactly, no transform needed. mDrawMatrix = null; } else if (ScaleType.CENTER == mScaleType) { // Center bitmap in view, no scaling. 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 { //FIT_START,FIT_END,FIT_CENTER // Generate the required transform. mTempSrc.set(0, 0, dwidth, dheight); mTempDst.set(0, 0, vwidth, vheight); mDrawMatrix = mMatrix; mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType)); } } } 复制代码
// Center bitmap in view, no scaling. mDrawMatrix = mMatrix; mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f), Math.round((vheight - dheight) * 0.5f)); 复制代码
mDrawMatrix矩阵平移,其实就是将图片移到ImageView的中间位置,展示图片的中间区域。直接进行平移。
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)); 复制代码
dwidth/dheight>vwidth/vheight(图片的宽高比大于ImageView的宽高比)等价于dwidth * vheight > vwidth * dheight ,也就是说ImageView和图片高度比小于ImageView和图片的宽度比,这时候取vheight/deight进行图片缩放,就能保证图片宽度在进行同等比例缩放的时候,图片宽度大于或等于ImageView的宽度,因为(vheight/dheight)* dwidth>vwidth。也解释了注释。先缩放,再平移。所以该模式和图片的宽高比以及ImageView的宽高比有关。
mDrawMatrix = mMatrix; 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); 复制代码
从scale = Math.min((float) vwidth / (float) dwidth, (float) vheight / (float) dheight); 可以看出该模式与(ImageView和图片的高度比)以及(ImageView和图片的宽度比)有关,哪个小scale就等于它。最后再进行缩放平移。
} else { //FIT_START,FIT_END,FIT_CENTER // Generate the required transform. mTempSrc.set(0, 0, dwidth, dheight); mTempDst.set(0, 0, vwidth, vheight); mDrawMatrix = mMatrix; mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType)); @UnsupportedAppUsage private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st) { // ScaleToFit enum to their corresponding Matrix.ScaleToFit values return sS2FArray[st.nativeInt - 1]; } private static final Matrix.ScaleToFit[] sS2FArray = { Matrix.ScaleToFit.FILL, Matrix.ScaleToFit.START, Matrix.ScaleToFit.CENTER, Matrix.ScaleToFit.END }; /** * Controlls how the src rect should align into the dst rect for setRectToRect(). */ public enum ScaleToFit { /** * Scale in X and Y independently, so that src matches dst exactly. This may change the * aspect ratio of the src. */ FILL(0), /** * Compute a scale that will maintain the original src aspect ratio, but will also ensure * that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. START * aligns the result to the left and top edges of dst. */ START(1), /** * Compute a scale that will maintain the original src aspect ratio, but will also ensure * that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. The * result is centered inside dst. */ CENTER(2), /** * Compute a scale that will maintain the original src aspect ratio, but will also ensure * that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. END * aligns the result to the right and bottom edges of dst. */ END(3); // the native values must match those in SkMatrix.h ScaleToFit(int nativeInt) { //关键方法 this.nativeInt = nativeInt; } final int nativeInt; } 复制代码
FILL:相当于ScaleType.FIX_XY,图片的高度和宽度有独立的缩放比例。图片宽度的缩放比例为vwidth/dwidth,高度的缩放比例为vheight/dheight。
下面是这篇网上一篇文章的对FIT_START,FIT_CENTER,FIT_END这三种类型的结论,但是并没有给出源码的分析,我按照它的结论测试了几个用例,发现并不对。所以我打算从源码角度来进行一下分析。
另外说一下,这篇文章写得还是很好的,前面的几个模式结合源码进行图文分析的很不错。
错误结论:
FIT_START,FIT_CENTER,FIT_END采用的缩放策略一样,如果dheight> dwidth,则使用vwidth/dwidth作为缩放比例,反之使用vheight/dheight作为缩放比例。
www.jianshu.com/p/fe5d2e3fe…
正确分析:
从源码我们可以看到FIT_START,FIT_CENTER,FIT_END这三个模式都调用了setRectToRect()
public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) { if (dst == null || src == null) { throw new NullPointerException(); } return nSetRectToRect(native_instance, src, dst, stf.nativeInt); } //而nSetRectToRect是一个nativie方法 @FastNative private static native boolean nSetRectToRect(long nObject, RectF src, RectF dst, int stf); 复制代码
最后我们找到framework层里面的真正实现
/** 33 * Delegate implementing the native methods of android.graphics.Matrix 34 * 35 * Through the layoutlib_create tool, the original native methods of Matrix have been replaced 36 * by calls to methods of the same name in this delegate class. 37 * 38 * This class behaves like the original native implementation, but in Java, keeping previously 39 * native data into its own objects and mapping them to int that are sent back and forth between 40 * it and the original Matrix class. 41 * 42 * @see DelegateManager 43 * 44 */ public final class Matrix_Delegate { ... ... ... 532 @LayoutlibDelegate 533 /*package*/ static boolean nSetRectToRect(long native_object, RectF src, 534 RectF dst, int stf) { 535 Matrix_Delegate d = sManager.getDelegate(native_object); 536 if (d == null) { 537 return false; 538 } 539 540 if (src.isEmpty()) { 541 reset(d.mValues); 542 return false; 543 } 544 545 if (dst.isEmpty()) { 546 d.mValues[0] = d.mValues[1] = d.mValues[2] = d.mValues[3] = d.mValues[4] = d.mValues[5] 547 = d.mValues[6] = d.mValues[7] = 0; 548 d.mValues[8] = 1; 549 } else { 550 float tx, sx = dst.width() / src.width(); 551 float ty, sy = dst.height() / src.height(); 552 boolean xLarger = false; 553 554 if (stf != ScaleToFit.FILL.nativeInt) { //FIT_START,FITA_END,FIT_CENTER 555 if (sx > sy) { 556 xLarger = true; 557 sx = sy; 558 } else { 559 sy = sx; 560 } 561 } 562 563 tx = dst.left - src.left * sx; 564 ty = dst.top - src.top * sy; 565 if (stf == ScaleToFit.CENTER.nativeInt || stf == ScaleToFit.END.nativeInt) { 566 float diff; 567 568 if (xLarger) { 569 diff = dst.width() - src.width() * sy; 570 } else { 571 diff = dst.height() - src.height() * sy; 572 } 573 574 if (stf == ScaleToFit.CENTER.nativeInt) { 575 diff = diff / 2; 576 } 577 578 if (xLarger) { 579 tx += diff; 580 } else { 581 ty += diff; 582 } 583 } 584 585 d.mValues[0] = sx; 586 d.mValues[4] = sy; 587 d.mValues[2] = tx; 588 d.mValues[5] = ty; 589 d.mValues[1] = d.mValues[3] = d.mValues[6] = d.mValues[7] = 0; 590 591 } 592 // shared cleanup 593 d.mValues[8] = 1; 594 return true; 595 } } 复制代码
如果要了解矩阵的原理和变化,可以看看下面这篇博文
www.jianshu.com/p/5e30db034…
juejin.im/post/5a27a5…
Matrix本质上是一个如下图所示的矩阵:
Matrix 提供了如下几个操作:
缩放(Scale) 对应 MSCALE_X 与 MSCALE_Y
位移(Translate) 对应 MTRANS_X 与 MTRANS_Y
错切(Skew) 对应 MSKEW_X 与 MSKEW_Y
旋转(Rotate) 旋转没有专门的数值来计算,Matrix 会通过计算缩放与错切来处理旋转。
FIT_START的分析:
float tx, sx = dst.width() / src.width(); 551 float ty, sy = dst.height() / src.height(); 552 boolean xLarger = false; if (stf != ScaleToFit.FILL.nativeInt) { //FIT_START,FITA_END,FIT_CENTER,所以下面这段实 //际上是FIT_START 555 if (sx > sy) { 556 xLarger = true; 557 sx = sy; 558 } else { 559 sy = sx; 560 } 561 } 562 563 tx = dst.left - src.left * sx; 564 ty = dst.top - src.top * sy; if (stf == ScaleToFit.CENTER.nativeInt || stf == ScaleToFit.END.nativeInt) { //FITA_END,FIT_CENTER ··· d.mValues[0] = sx; 586 d.mValues[4] = sy; 587 d.mValues[2] = tx; 588 d.mValues[5] = ty; 589 d.mValues[1] = d.mValues[3] = d.mValues[6] = d.mValues[7] = 0; 复制代码
可以看出当(图片和ImageView的宽度比)大于(图片和ImageView的高度比)时,缩放比等于图片和ImageView的宽度比,反之也成立,x轴平移的距离为dst.left - src.left * sx,y轴平移的距离为dst.top - src.top * sy,因为矩阵的0,4位置是缩放的,2,5位置是平移。
FIT_END,FIT_CENTER的分析:与FIT_START一样,缩放比也是和图片和Imageview的宽度比(高度比)相关,只是位移做了一些调整,这里就不细究了。
我们的猜测结论是错误的,因为我们的测试的例子不能够覆盖所有场景,所以导致得出的结论有偏差,只适应于一定的范围,所以,看源码,看源码,看源码!
FIT_START,FIT_END,FIT_CENTER的缩放比都和图片与ImageView的宽度比(高度比)相关,然后在进行位移,图片会保持原有的比例。
FIT_XY,直接缩放到ImageView的宽高,会破坏原有的图片比例。
CENTER,其实就是将图片移到ImageView的中间位置,展示图片的中间区域。直接进行平移,不缩放,多余的进行剪裁。
CENTER_CROP,先缩放,再平移。该模式和图片的宽高比以及ImageView的宽高比有关,而不是图片和ImageView的宽度比(高度比),注意区别。
CENTER_INSIDE,该模式与(ImageView和图片的高度比)以及(ImageView和图片的宽度比)有关,哪个小scale就等于它。最后再进行缩放平移。
MATRIX,不改变原图的大小,从ImageView的左上角开始绘制,超出的位置做剪切处理。
我们可以设计一种模式,让图片显示在底部中间,如果图片超过ImageView的大小,那就剪裁掉多余部分,保留底部。
效果图如下:无论ImageView多高多宽,图片永远显示在底部中间,多余的剪裁掉。
代码如下:
//设置初始矩阵值 Matrix matrix = new Matrix(); float matrixValues[] = { 1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f }; matrix.setValues(matrixValues); //获取ImageView宽高 vWidth = img_test3.getWidth(); vHeight = img_test3.getHeight(); //获取图片固有宽高,与源码的获取方式相同,一定要使用该方式 dWidth = img_test3.getDrawable().getIntrinsicWidth(); dHeight = img_test3.getDrawable().getIntrinsicHeight(); // BitmapFactory.Options options = new BitmapFactory.Options(); // BitmapFactory.decodeResource(getResources(),R.drawable.ic_test_3,options); // //获取图片的宽高,这个方式不行,注意! // dHeight = options.outHeight; // dWidth = options.outWidth; //对图片进行平移 float tx3 = Math.round((vWidth - dWidth) * 1 * 0.5); float ty3 = Math.round((vHeight - dHeight) * 1); matrix.setTranslate(tx3,ty3); img_test3.setImageMatrix(matrix); 复制代码
带有个需要注意的地方,就是ImageView要设置scaleType方式为matrix,这样才能生效。源码如下:
if (ScaleType.MATRIX == mScaleType) { // Use the specified matrix as-is. if (mMatrix.isIdentity()) { mDrawMatrix = null; } else { mDrawMatrix = mMatrix; } 复制代码
参考文章
Android Matrix详解:www.jianshu.com/p/5e30db034…
ImageView之ScaleType详解及拓展:juejin.im/post/5a27a5…
ImageView的ScaleType原理及效果分析:www.jianshu.com/p/fe5d2e3fe…