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

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永远不会被用上了?如有理解的朋友还望告知。


欢迎分享交流
邮箱:liuj4563@163.com
原创博文 转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容