圖片基礎知識梳理(1) - ImageView 的 ScaleType 屬性解析

一谦炒、概述

在使用ImageView的過程當中积暖,經(jīng)常需要通過scaleType來對原始的圖像進行處理,使得它能在空間中合理地展示孙咪。

二堪唐、scaleType的分類

首先,我們簡單介紹一下scaleType的分類:

2.1 通過Matrix設置

這種情況下翎蹈,對應的模式只有一種:

  • ScaleType.MATRIX

最終淮菠,在這種情況下,我們可以同setImageMatrix(Matrix matrix)來改變荤堪。

2.2 填充類型

這一類屬性的特點就是通過拉伸或者壓縮圖片合陵,使得原圖片中所有元素都能夠展現(xiàn),并且至少填滿控件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軸上的元素至少有一個在控件中能被完全展示,那么有一下兩種情況屋摔,在下面的操作做完之后烁设,裁剪掉多余的部分:

    • 如果原圖沒有填滿控件,那么會慢慢按比例放大钓试,直到填滿控件装黑;
    • 如果原圖已經(jīng)填滿控件,那么它會慢慢縮小弓熏,直到某一邊和控件重合恋谭。
  • ScaleType.CENTER_INSIDE
    要求三點:

  • 原圖的所有像素位于控件內(nèi)部

  • 原圖的比例不變

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

它不要求原始圖片填滿x,y軸的任意一個挽鞠,因此疚颊,如果原圖的長寬都小于等于控件的長寬狈孔,不會進行放大操作,這也是它和ScaleType.FIT_CENTER的區(qū)別材义。

三均抽、示例

下面,我們通過一個簡單的Demo來展示一下各種類型的具體表現(xiàn)其掂,我們有兩個大小一樣的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當中,設置圖片的接口主要有下面幾個函數(shù):

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為例贤牛,分析一下它整個的流程:

  • 第一步:當我們調(diào)用setImageBitmap之后惋鹅,它會把Bitmap封裝在BitmapDrawable當中,之后調(diào)用了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);
    }
  • 第二步:調(diào)用setImageDrawable
    public void setImageDrawable(@Nullable Drawable drawable) {
        //如果不是同一個資源.
        if (mDrawable != drawable) {
            mResource = 0;
            mUri = null;
            //舊的寬高.
            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;
            //關(guān)鍵方法
            updateDrawable(drawable);
            //如果寬高不同盔夜,才請求重新布局.
            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout();
            }
            //只要替換了資源就需要重新繪制.
            invalidate();
        }
    }

上面的關(guān)鍵方法在updateDrawable當中:

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

4.3 configureBounds改變Matrix

configureBounds里就會根據(jù)我們所配置的scaleType來決定mDrawable如何顯示负饲,在這里面有一個重要的變量mDrawMatrix,我們前面說到的所有變換都是通過它來實現(xiàn)的喂链,當然返十,我們除了可以讓系統(tǒng)自己根據(jù)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.表示原始資源已經(jīng)能夠填滿控件.
        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) {
                //如果原始資源已經(jīng)填滿控件迟杂,那么不需要考慮其它的變換了.
                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 {
                //設置兩個區(qū)域的大小.
                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();
            //創(chuàng)建一個新的圖層.
            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);
            //根據(jù)mDrawMatrix進行變換.
            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            //再在這個變換上面繪制我們的資源.
            mDrawable.draw(canvas);
            //把合成完成的圖片繪制上去.
            canvas.restoreToCount(saveCount);
        }
    }

4.5 小結(jié)

我們總結(jié)一下藤违,整個scaleType的原理就是在configureBounds中配置了mDrawMatrix浪腐,而在onDraw當中會根據(jù)mDrawMatrix來對圖層進行變換,在這個變換之后的圖層上進行繪制mDrawable顿乒,之后再恢復圖層议街。

五、ImageViewsrcbackground的區(qū)別

上面璧榄,我們看到的都是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);
        }
    }

我們來看一下設置背景的邊界的函數(shù)访得,可以看到龙亲,這里沒有考慮padding值,也就是說我們通過background設置的圖片是填滿整個控件悍抑,并且不考慮padding的:

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

最后再結(jié)合一下第四節(jié)的知識鳄炉,我們是先繪制背景,然后才在ImageViewonDraw函數(shù)當中在canvas上繪制的搜骡,因此轻腺,src的圖片一定會繪制在backgroud之上辩尊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末歉备,一起剝皮案震驚了整個濱河市瓦戚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摸吠,老刑警劉巖空凸,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寸痢,居然都是意外死亡呀洲,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門啼止,熙熙樓的掌柜王于貴愁眉苦臉地迎上來道逗,“玉大人,你說我怎么就攤上這事献烦∽仪希” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵巩那,是天一觀的道長吏夯。 經(jīng)常有香客問我,道長拢操,這世上最難降的妖魔是什么锦亦? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任舶替,我火速辦了婚禮令境,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘顾瞪。我一直安慰自己舔庶,他們只是感情好抛蚁,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惕橙,像睡著了一般瞧甩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弥鹦,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天肚逸,我揣著相機與錄音,去河邊找鬼彬坏。 笑死朦促,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的栓始。 我是一名探鬼主播务冕,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼幻赚!你這毒婦竟也來了禀忆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤落恼,失蹤者是張志新(化名)和其女友劉穎箩退,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體领跛,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡乏德,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吠昭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喊括。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖矢棚,靈堂內(nèi)的尸體忽然破棺而出郑什,到底是詐尸還是另有隱情,我是刑警寧澤蒲肋,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布蘑拯,位于F島的核電站,受9級特大地震影響兜粘,放射性物質(zhì)發(fā)生泄漏申窘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一孔轴、第九天 我趴在偏房一處隱蔽的房頂上張望剃法。 院中可真熱鬧,春花似錦路鹰、人聲如沸贷洲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽优构。三九已至诵叁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钦椭,已是汗流浹背拧额。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留彪腔,地道東北人势腮。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像漫仆,于是被迫代替她去往敵國和親捎拯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容