ImageView中ScaleType源码分析

原文发布于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。
我们接着看这个方法:

  1. 判断dwidth与dheight(从mDrawable的获得的宽高)有没有意义,或者mScaleType的值是否为ScaleType.FIT_XY,OK 其中一个,其调用的代码为:

            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
    

    即按照ImageView内部空间大小设置mDrawable的大小,所以ScaleType.FIT_XY的效果 是伸缩至恰好填充控件

  2. 进入另一个大分支,首先将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变换,否则不会变换,即按照资源自身大小绘制。

  3. 下一个的是**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的中间。

  4. 接下来是 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。且居中。

  5. 下一个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特性我们可以总结:

    • 图片宽高都小于控件,不缩放,居中
    • 图片宽高只要有一方大于控件的。就尽量缩小,图片会完全显示在控件内,居中。
  6. 终于到了最后一个分支:

        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_*都在此了。去看下这个方法:

ImageView中ScaleType源码分析_第1张图片
03-08-48.jpg


代码特别简单,就是根据mScaleType枚举类型的值去数组sS2FArray里面拿到对应的类型的值。sS2FArray[st.nativeInt - 1],ScaleType.FIT_START.nativeInt的值是2,2-1正好是Matrix.ScaleToFit.START在数组中的位置,其它也同样。所以这三个FIT_都是通过 mDrawMatrix.setRectToRect()方法作用于矩阵来改变视图的。这三种效果都特别简单,与FIT_XY不同的是,它们不会在两个方向上对图片拉伸,会 保持图片比例,高度与控件内部一致,三个属性分别对应开始、中间、末尾(一般是左、中、右,是按照文字阅读方向)三个位置。**


我们已经获得了变换后的矩阵mDrawMatrix,最终在onDraw()方法中使用:


10-27-21.jpg

至此,我们的源码分析已经结束了。

最后一个小疑惑
在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]
原创博文 转载请注明出处。

你可能感兴趣的:(ImageView中ScaleType源码分析)