Android ImageView 之 ScaleType 詳解

基礎(chǔ)儲備

在 ImageView 中有一個成員變量mDrawMatrix,這個變量是Matrix(矩陣)類型,我們了解一下這個Matrix類,Matrix 常見的方法有setScale(sx,sy) ,setTranslate(float dx, float dy)等方法. 在設(shè)置了mDrawMatrix的一系列方法后, 當ImageView方法里的 onDraw(canvas)執(zhí)行時候,canvas 會根據(jù)mDrawMatrix設(shè)置的值對圖片資源(bitmap)的繪制進行相應(yīng)的變換.
例如:

  • 當mDrawMatrix.setTranslate(10,10)時,在執(zhí)行 ImageView 的 onDraw(canvas)方法時候,canvas 在對圖片(bitmap)進行繪制的時候, 會將圖片資源在在 x,y 軸兩個方向上分別偏移10px.
  • 當mDrawMatrix.setScale(1.5f,1.5f)時,canvas 在對圖片(bitmap)進行繪制的時候, 會將圖片資源放大1.5f 倍

基礎(chǔ)儲備部分一定要理解,也是下面文章的核心部分

Tips

在我們實驗的時候我們要考慮圖片與 ImageView的以下幾種關(guān)系:

  1. 圖片的寬高大于 ImageView 的寬高
  2. 圖片的寬高小于 ImageView 的寬高
  3. 圖片的寬大于 ImageView 的寬,圖片的高小于 ImageView 的高
  4. 圖片的高大于 ImageView 的高,圖片的寬小于 ImageView 的寬
    不同的情況得到的結(jié)果也可能會不一樣.

setScaleType()是什么?

在平常開發(fā)中我們加載得到圖片有可能與 ImageView 的大小不一樣(如果 ImageView 大小是固定的),圖片在 ImageView中應(yīng)該怎么顯示?這就需要我們使用setScaleType(ScaleType scaleType)去設(shè)置,該方法主要用于調(diào)整圖片大小來適應(yīng) ImageView 的大小,ScaleType 是 ImageView 的一個內(nèi)部枚舉類型,通過源碼該枚舉類有如下八個類型:

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;
}

ImageView的默認的ScaleType 是什么?

在 ImageView 的構(gòu)造方法中調(diào)用了initImageView()方法,在該方法中我們看到 ImageView 的 mScaleType 的默認值是 FIT_CENTER

private void initImageView() {    
            mMatrix     = new Matrix();   
            mScaleType  = ScaleType.FIT_CENTER;    
            ........
 }

setImageDrawable()執(zhí)行過程

setScaleType()方法設(shè)置的ScaleType值不同, 可以得到圖片在 ImageView 中不同的顯示結(jié)果.當為 ImageView設(shè)置一個圖片資源的時候使用setImageDrawable(drawable)

    public void setImageDrawable(@Nullable Drawable drawable) {
            ...... 
            updateDrawable(drawable);
            ......
            //調(diào)用 onDraw()方法,重新繪制
            invalidate();
    }

setImageDrawable()方法調(diào)用了updateDrawable(drawable)方法,保存了 drawable 的寬和高.

    private void updateDrawable(Drawable d) {    
        ....
        mDrawable = d;
        if (d != null) {        
            ......
            //mDrawable 的寬度
            mDrawableWidth = d.getIntrinsicWidth();            
           //mDrawable 的高度
            mDrawableHeight = d.getIntrinsicHeight();          
            ......
            configureBounds();
        } else {
            mDrawableWidth = mDrawableHeight = -1;          
        }
    }

接下來進入configureBounds()方法

    private void configureBounds() {
            ......
        //mDrawable 寬度
        int dwidth = mDrawableWidth;
        //mDrawable 高度
        int dheight = mDrawableHeight;
        //當前控件的寬度
        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        //當前控件的高度
        int vheight = getHeight() - mPaddingTop - mPaddingBottom;
        boolean fits = (dwidth < 0 || vwidth == dwidth)&&(dheight < 0 || vheight == dheight);
        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            /* 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);
            mDrawMatrix = null;
        } else {
            // We need to do the scaling ourself, so have the drawable
            // use its native size.
            //根據(jù)不同的類型對mDrawMatrix做不同的處理
            mDrawable.setBounds(0, 0, dwidth, dheight);
            if (ScaleType.MATRIX == mScaleType) {
                ......
            } else if (fits) {
                ......
            } else if (ScaleType.CENTER == mScaleType) {
                ......
            }else if (ScaleType.CENTER_CROP == mScaleType) {   
                ......
            }else if (ScaleType.CENTER_INSIDE == mScaleType) {
                ......
            }else{
                ......
            }
    }

在configureBounds()方法中我們看了一系列的對mScaleType值的判斷,根據(jù)mScaleType值得不同對mDrawMatrix做一些處理.

setImageDrawable()方法中有這么一行代碼invalidate();我們知道調(diào)用invalidate()方法會重新執(zhí)行onDraw()方法.在前面我們已經(jīng)說過,在 onDraw()方法里會根據(jù)mDrawMatrix對圖片的繪制進行一系列的轉(zhuǎn)換.

  • ScaleType.CENTER
    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));
    }

根據(jù)mScaleType == ScaleType.CENTER部分的代碼,我們可以知道canvas 在繪制圖片的時候會對圖(bitmap)片進行偏移

Untitled.png

結(jié)論:當為 ImageView 的 mScaleType == ScaleType.CENTER 時候,圖片不放大也不縮小,圖片在控件中居中顯示,超出ImageView 部分不顯示

  • ScaleType.CENTER_CROP
    else if (ScaleType.CENTER_CROP == mScaleType) {
    mDrawMatrix = mMatrix;
    float scale;
    float dx = 0, dy = 0;
    if (dwidth/vwidth > dheight/vheight) {
    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));
    }

從代碼我們可以看到當繪制 bitmap 的時候,會對圖片先進性縮放,縮放比例為 scale,再對圖片進行偏移,偏移量為 dx,dy.
結(jié)論:圖片的寬高一定會大于或者等于 ImageView 的寬高.居中顯示,超出 ImageView 部分不顯示. 圖片的寬(高)至少有一個等于 ImageView 的寬(高)

CENTER_CROP.png

  • 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);
    }

從代碼上述代碼中,根據(jù)mDrawMatrix 我們可以判斷,當 ImageView 的onDraw(canvas)方法對圖片(bitmap)進行繪制的時候,會對圖片先進性縮放,在進行位移操作.if (dwidth <= vwidth && dheight <= vheight),當圖片的寬高<ImageView的寬高時,圖片不進行縮放,圖片只居中顯示.
結(jié)論: 當mScaleType的值為ScaleType.CENTER_INSIDE時,圖片的寬高一定小于或者等于 ImageView 控件的寬高,圖片居中顯示.

  • ScaleType.MATRIX
    if (ScaleType.MATRIX == mScaleType) {
    // Use the specified matrix as-is.
    if (mMatrix.isIdentity()) {
    mDrawMatrix = null;
    } else {
    mDrawMatrix = mMatrix;
    }
    }
    從mScaleType == ScaleType.MATRIX的代碼中,將mMatrix賦值給了mDrawMatrix.可以通過調(diào)用 ImageView 的setImageMatrix(matrix)方法對mMatrix進行賦值.因此我們對 mMatrix做了什么操作,ImageView在 onDraw()方法在對圖片進行繪制的時候就會對圖片進行什么操作.

  • Other (一般值為ScaleType.FIT....)

      else {
         // Generate the required transform. 
         mTempSrc.set(0, 0, dwidth, dheight);    
         mTempDst.set(0, 0, vwidth, vheight);        
         mDrawMatrix = mMatrix;    
         mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
      }
    

    當mScaleType是ScaleType.FIT.... 類型的時候,系統(tǒng)并沒有直接顯式mDrawMatrix進行操作,而是調(diào)用了setRectToRect(RectF src, RectF dst, ScaleToFit stf)方法

      public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {      
          return native_setRectToRect(native_instance, src, dst, stf.nativeInt);
      }
    

在該方法中本地方法native_setRectToRect(native_instance, src, dst, stf.nativeInt);因此我們可以猜想可能是在本地方法中對mDrawMatrix做了一系列操作.下面給出ScaleType.FIT...結(jié)論

  • FIT_START
    結(jié)論:圖片的寬(高)一定小于或等于 ImageView 的寬高,圖片的寬(高)至少有一個等于ImageView的寬(高),圖片顯示在 ImageView 的左(上)
  • FIT_CENTER
    結(jié)論:圖片的寬(高)一定小于或等于 ImageView 的寬高,圖片的寬(高)至少有一個等于ImageView的寬(高),圖片居中顯示在 ImageView 中
  • FIT_END
    結(jié)論:圖片的寬(高)一定小于或等于 ImageView 的寬高,圖片的寬(高)至少有一個等于ImageView的寬(高),圖片顯示在 ImageView 的右(下)

總結(jié)

本片文章中并沒有列舉例子取驗證我們的結(jié)果.如果想要很好的了解,需要用戶自己動手根據(jù)結(jié)論去驗證,根據(jù)源碼自己理解一下,當自己親身試驗一遍后,相信肯定會有所收獲.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

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