Android开发

全面分析ImageView的ScaleType(源码)

本文主要是介绍全面分析ImageView的ScaleType(源码),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

ScaleType作为一个枚举类型,一共定义了八个模式

八个ScaleType,其实可以分为三个类型:

  • FIT_开头的4种,它们的共同点是都会对图片进行缩放。

  • CENTER_开头的三种,它们的共同点是居中显示,图片的中心点会与ImageView的中心点重叠。

  • MATRIX,不改变原图的大小,从ImageView的左上角开始绘制,超出的位置做剪切处理;原图小于ImageView则只绘制原图大小,其他地方空白。

本文的分析先进行实战猜测,根据实际结果猜测一个结论,最后看源码进行验证猜测的结论,所以猜测的结论不一定正确,如果不想看猜测过程,可以直接跳到源码分析环节,直接找到正确的结论。

FIT_开头的四种类型

  • 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的中间。

  • FIT_XY(不保持原图片宽高比例)

​ 把图片放大/缩小到ImageView的大小,不保持原图片比例,简单粗暴。

小结

​ 对于FIT_START,FIT_END,FIT_CENTER来说,图片的宽和高哪个更接近ImageView对应的宽和高,哪个(宽/高)就先缩放到ImageView的大小,然后另一个按前面的比例进行缩放,会保持图片的原宽高比例,而且一定会显示完整的图片,就是图片的所有部分,不会造成放大或缩小后只显示图片的一部分内容;FIX_XT就是简单粗暴的缩放到ImageView的大小,不会保持图片的原宽高比例,但是也会显示图片的所有内容

所以FIT_开头的方式都能显示图片的所有内容。

PS:显示图片的所有内容意思就是,把图片上所有东西都显示出来,有些放大/缩小只是放大/缩小一部分内容,比如放大上面那个图片的打电脑的女生,然后放大到充满ImageView,但是我们就看不到LeetCode这段文字了,这样就不能显示图片的所有内容。

CENTER_开头的三种类型(缩放中心点是图片的中心点)

  • CENTER_CROP

    本来以为缩放也会和宽高有关,但是看到下面这个图就猜不出具体是什么关系了,只能在后面的源码分析里面看一下是怎么缩放的。

  • CENTER_INSIDE(保持原图片宽高比例)

    以原图正常显示为目的,如果原图大小大于ImageView的size,就按照比例缩小原图的宽高,直到较长的一端刚好能显示在ImageView中,居中显示在ImageView中。如果原图size小于ImageView的size,则不做处理居中显示图片。如下图所示:

  • CENTER

    保持原图的大小,显示在ImageView的中心。当原图的size大于ImageView的size时,多出来的部分被截掉。

MATRIX

不改变原图的大小,从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));
            }
        }
    }
复制代码
  1. CENTER
// Center bitmap in view, no scaling.
                mDrawMatrix = mMatrix;
                mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
                                         Math.round((vheight - dheight) * 0.5f));
复制代码

mDrawMatrix矩阵平移,其实就是将图片移到ImageView的中间位置,展示图片的中间区域。直接进行平移。

  1. CENTER_CROP
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的宽高比有关。

  1. CENTER_INSIDE
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就等于它。最后再进行缩放平移。

  1. 当ImageView设置了四种ScaleType后,ImageView采用Matrit.ScaleToFit来进行图片的展示。
} 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_XMSCALE_Y

  • 位移(Translate) 对应 MTRANS_XMTRANS_Y

  • 错切(Skew) 对应 MSKEW_XMSKEW_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…

这篇关于全面分析ImageView的ScaleType(源码)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!