图片基础知识梳理(1) - ImageView 的 ScaleType 属性解析

一、概述

在使用ImageView的过程当中,经常需要通过scaleType来对原始的图像进行处理,使得它能在空间中合理地展示。

二、scaleType的分类

首先,我们简单介绍一下scaleType的分类:

2.1 通过Matrix设置

这种情况下,对应的模式只有一种:

  • ScaleType.MATRIX

最终,在这种情况下,我们可以同setImageMatrix(Matrix matrix)来改变。

2.2 填充类型

这一类属性的特点就是通过拉伸或者压缩图片,使得原图片中所有元素都能够展现,并且至少填满控件x,y轴的其中一个
一共有四类:

  • ScaleType.FIX_XY不考虑原图的比例,拉伸或者压缩使得它等于控件的宽高。

下面三种都会维持原图的比例,使得它们的x,y都小于等于控件的宽高,只是最终的图形放的位置不同。

  • ScaleType.FIT_START:放置在左上角。
  • ScaleType.FIT_CENTER:放置在中间。
  • ScaleType.FIT_END:放置在右下角。

2.3 中心重合类型

下面的三种类型都会使得控件的中心和图片中心重合:

  • ScaleType.CENTER
    要求一点:

  • 原图的中心和控件的中心重合

  • ScaleType.CENTER_CROP
    要求三点:

  • 整个控件能够被填满

  • 原图的比例不变

  • 原图的中心和控件的中心重合。

  • 保证原图的x,y轴上的元素至少有一个在控件中能被完全展示,那么有一下两种情况,在下面的操作做完之后,裁剪掉多余的部分:

    • 如果原图没有填满控件,那么会慢慢按比例放大,直到填满控件;
    • 如果原图已经填满控件,那么它会慢慢缩小,直到某一边和控件重合。
  • ScaleType.CENTER_INSIDE
    要求三点:

  • 原图的所有像素位于控件内部

  • 原图的比例不变

  • 图片的中心和控件的中心重合。

它不要求原始图片填满x,y轴的任意一个,因此,如果原图的长宽都小于等于控件的长宽,不会进行放大操作,这也是它和ScaleType.FIT_CENTER的区别

三、示例

下面,我们通过一个简单的Demo来展示一下各种类型的具体表现,我们有两个大小一样的ImageView和两个大小不同的原图,其中左边的ImageView要比原图小,右边的ImageView要比原图大。

  • ScaleType.FIX_XY
  • ScaleType.FIT_START
  • ScaleType.FIT_CENTER
  • ScaleType.FIT_END
  • ScaleType.CENTER
  • ScaleType.CENTER_CROP
  • ScaleType.CENTER_INSIDE

四、源码分析

4.1 给ImageVIew设置src的接口

ImageView当中,设置图片的接口主要有下面几个函数:

public void setImageBitmap(Bitmap bm)
public void setImageResource(@DrawableRes int resId)
public void setImageURI(@Nullable Uri uri)
public void setImageDrawable(@Nullable Drawable drawable)

4.2 setImageBitmap的流程

我们就以平时常用的setImageBitmap为例,分析一下它整个的流程:

  • 第一步:当我们调用setImageBitmap之后,它会把Bitmap封装在BitmapDrawable当中,之后调用了setImageDrawable(Drawable drawable)方法:
    public void setImageBitmap(Bitmap bm) {
        mDrawable = null;
        if (mRecycleableBitmapDrawable == null) {
            mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
        } else {
            mRecycleableBitmapDrawable.setBitmap(bm);
        }
        setImageDrawable(mRecycleableBitmapDrawable);
    }
  • 第二步:调用setImageDrawable
    public void setImageDrawable(@Nullable Drawable drawable) {
        //如果不是同一个资源.
        if (mDrawable != drawable) {
            mResource = 0;
            mUri = null;
            //旧的宽高.
            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;
            //关键方法
            updateDrawable(drawable);
            //如果宽高不同,才请求重新布局.
            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout();
            }
            //只要替换了资源就需要重新绘制.
            invalidate();
        }
    }

上面的关键方法在updateDrawable当中:

    private void updateDrawable(Drawable d) {
        //....
        if (d != null) {
            //...
            //这里面根据scaleType配置mDrawMatrix.
            configureBounds();
        } else {
            mDrawableWidth = mDrawableHeight = -1;
        }
    }

4.3 configureBounds改变Matrix

configureBounds里就会根据我们所配置的scaleType来决定mDrawable如何显示,在这里面有一个重要的变量mDrawMatrix,我们前面说到的所有变换都是通过它来实现的,当然,我们除了可以让系统自己根据scaleType来生成matrix,也可以通过setImageMatrix手动的指定自己的变换:

    private void configureBounds() {
        if (mDrawable == null || !mHaveFrame) {
            return;
        }
      //1.得到原始资源的宽高.
        final int dwidth = mDrawableWidth;
        final int dheight = mDrawableHeight;
        //2.得到控件的宽高,这里去掉了控件的padding.
        final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        final int vheight = getHeight() - mPaddingTop - mPaddingBottom;

        //3.表示原始资源已经能够填满控件.
        final boolean fits = (dwidth < 0 || vwidth == dwidth)
                && (dheight < 0 || vheight == dheight);

        //4.假如有一边是wrap_content,或者是FIX_XY,那么填满整个控件.
        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            mDrawable.setBounds(0, 0, dwidth, dheight);
            //如果scaleType是matrix.
            if (ScaleType.MATRIX == mScaleType) {
                //单位矩阵的情况,设为null.
                if (mMatrix.isIdentity()) {
                    mDrawMatrix = null;
                } else {
                    //否则最后的DrawMatrix就是我们传入的Matrix.
                    mDrawMatrix = mMatrix;
                }
            } else if (fits) {
                //如果原始资源已经填满控件,那么不需要考虑其它的变换了.
                mDrawMatrix = null;
            } else if (ScaleType.CENTER == mScaleType) {
                //当scaleType为center的时候.
                mDrawMatrix = mMatrix;
                //移动到中心,初始时候,控件和原始资源的(0,0)点是重合的.
                mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f), Math.round((vheight - dheight) * 0.5f));
            } else if (ScaleType.CENTER_CROP == mScaleType) {
                mDrawMatrix = mMatrix;
                //当scaleType是centerCrop的时候.
                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) {
                //当scaleType是centetInside时
                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;
                //这里处理FIX_START,FIX_END,FIX_CENTER的情况.
                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
            }
        }
    }

4.4 onDraw中进行绘制

那么这个mDrawMatrix是在什么时候使用的呢,我们看一下onDraw方法:

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
            mDrawable.draw(canvas);
        } else {
            final int saveCount = canvas.getSaveCount();
            //创建一个新的图层.
            canvas.save();
            if (mCropToPadding) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                        scrollX + mRight - mLeft - mPaddingRight,
                        scrollY + mBottom - mTop - mPaddingBottom);
            }
            //移动到去掉padding的左上角.
            canvas.translate(mPaddingLeft, mPaddingTop);
            //根据mDrawMatrix进行变换.
            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            //再在这个变换上面绘制我们的资源.
            mDrawable.draw(canvas);
            //把合成完成的图片绘制上去.
            canvas.restoreToCount(saveCount);
        }
    }

4.5 小结

我们总结一下,整个scaleType的原理就是在configureBounds中配置了mDrawMatrix,而在onDraw当中会根据mDrawMatrix来对图层进行变换,在这个变换之后的图层上进行绘制mDrawable,之后再恢复图层。

五、ImageViewsrcbackground的区别

上面,我们看到的都是src设置的效果,我们回忆一下,通过设置android:background也可以设置一个图片给它,其实backgroundView的属性,在我们之前分析View的绘制流程的时候,draw(canvas)中有一步就是绘制背景:

    private void drawBackground(Canvas canvas) {
        //1.设置背景的边界.
        setBackgroundBounds();
       //2.如果有滚动,那么背景需要相应的滚动.
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            //3.绘制背景.
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }

我们来看一下设置背景的边界的函数,可以看到,这里没有考虑padding值,也就是说我们通过background设置的图片是填满整个控件,并且不考虑padding的:

    void setBackgroundBounds() {
        if (mBackgroundSizeChanged && mBackground != null) {
            //没有考虑padding部分.
            mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
            mBackgroundSizeChanged = false;
            rebuildOutline();
        }
    }

最后再结合一下第四节的知识,我们是先绘制背景,然后才在ImageViewonDraw函数当中在canvas上绘制的,因此,src的图片一定会绘制在backgroud之上。

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

推荐阅读更多精彩内容