Android源碼系列之深入理解ImageView的ScaleType屬性_Android

來源:互聯網
上載者:User

做Android開發的童靴們肯定對系統內建的控制項使用的都非常熟悉,比如Button、TextView、ImageView等。如果你問我具體使用,我會給說:拿ImageView來說吧,首先建立一個新的項目,在項目布局檔案中應用ImageView控制項,代碼如下:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:background="#bbaacc" >   <ImageView  android:src="@drawable/ic_launcher"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:background="#aabbcc" />  </LinearLayout> 

        上邊布局檔案為了便於查看各種屬性效果,故意加了兩個背景顏色,這對我們今天的源碼分析影響不大。接著運行一下代碼,效果圖如下:

        恩,不錯,運行結果正如所願,螢幕上顯示的正是我們設定的圖片,這時候心中不由欣喜ImageView的使用就是這樣簡單,so easy嘛!呵呵,如果真是這麼想那就大錯特錯了,上邊的樣本僅僅是在布局檔案中使用了ImageView的src,layout_width和layout_height這三個屬性罷了,它其他的重要屬性我們還沒有用到,今天這篇文章就是主要結合源碼講解ImageView的另一個重要的屬性------ScaleType,其他的一些屬性等將來需要的話再做詳細解說。好了,現在正式進入主題。
        ScaleType屬性主要是用來定義圖片(Bitmap)如何在ImageView中展示的,姑且就認為是展示吧,系統給我們提供了8種可選屬性:matrix、fitXY、fitStart、fitCenter、fitEnd、center、centerCrop和centerInside。每一種屬性對應的展示效果是不一樣的,下面我們先來做一個實驗來說明每一種屬性的顯示效果,我從之前的項目中挑選了兩張圖片,一張圖片的實際尺寸是720*1152,另一張是我把第一張圖翻轉放縮得到的,它的實際尺寸是96*60,之所以採用兩張圖片是為了便於對比和觀察結果,原圖片如下:

        OK,測試圖片準備好了,接下來我們在布局檔案中分別使用ScaleType的每一個屬性值,我們開始寫布局檔案,內容如下:

<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:background="#bbccaa" >   <TableLayout  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:padding="10dp" >   <TextView  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:layout_marginBottom="10dp"  android:text="圖片尺寸的寬和高都遠遠大於ImageView的尺寸" />   <TableRow  android:layout_width="match_parent"  android:layout_height="wrap_content" >   <ImageView  android:layout_width="300px"  android:layout_height="300px"  android:background="#aabbcc"  android:scaleType="matrix"  android:src="@drawable/test" />   <ImageView  android:layout_width="300px"  android:layout_height="300px"  android:layout_marginLeft="10dp"  android:background="#aabbcc"  android:scaleType="fitXY"  android:src="@drawable/test" />   <ImageView  android:layout_width="300px"  android:layout_height="300px"  android:layout_marginLeft="10dp"  android:background="#aabbcc"  android:scaleType="fitStart"  android:src="@drawable/test" />   <ImageView  android:layout_width="300px"  android:layout_height="300px"  android:layout_marginLeft="10dp"  android:background="#aabbcc"  android:scaleType="fitCenter"  android:src="@drawable/test" />  </TableRow>   <TableRow  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_marginTop="5dp" >   <TextView  android:layout_width="300px"  android:layout_height="wrap_content"  android:gravity="center"  android:text="matrix" />   <TextView  android:layout_width="300px"  android:layout_height="wrap_content"  android:gravity="center"  android:text="fitXY" />   <TextView  android:layout_width="300px"  android:layout_height="wrap_content"  android:gravity="center"  android:text="fitStart" />   <TextView  android:layout_width="300px"  android:layout_height="wrap_content"  android:gravity="center"  android:text="fitCenter" />  </TableRow>   <TableRow  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_marginTop="10dp" >   <ImageView  android:layout_width="300px"  android:layout_height="300px"  android:background="#aabbcc"  android:scaleType="fitEnd"  android:src="@drawable/test" />   <ImageView  android:layout_width="300px"  android:layout_height="300px"  android:layout_marginLeft="10dp"  android:background="#aabbcc"  android:scaleType="center"  android:src="@drawable/test" />   <ImageView  android:layout_width="300px"  android:layout_height="300px"  android:layout_marginLeft="10dp"  android:background="#aabbcc"  android:scaleType="centerCrop"  android:src="@drawable/test" />   <ImageView  android:layout_width="300px"  android:layout_height="300px"  android:layout_marginLeft="10dp"  android:background="#aabbcc"  android:scaleType="centerInside"  android:src="@drawable/test" />  </TableRow>   <TableRow  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:layout_marginTop="5dp" >   <TextView  android:layout_width="300px"  android:layout_height="wrap_content"  android:gravity="center"  android:text="fitEnd" />   <TextView  android:layout_width="300px"  android:layout_height="wrap_content"  android:gravity="center"  android:text="center" />   <TextView  android:layout_width="300px"  android:layout_height="wrap_content"  android:gravity="center"  android:text="centerCrop" />   <TextView  android:layout_width="300px"  android:layout_height="wrap_content"  android:gravity="center"  android:text="centerInside" />  </TableRow>  </TableLayout>  </ScrollView> 

        為了快速進入今天文章主題,布局檔案並沒有按照平時開發中所遵循的Android開發規範來寫。布局中使用的是TableLayout標籤嵌套TableRow的方式,分成兩行,每一行是4列,恰好8種屬性可以合理對比查看。當然了這裡有更高效的寫法來實現同樣的效果,比如使用RelativeLayout布局等。
        在布局檔案中我們定義了每個ImageView的寬高都是固定的300像素,這個尺寸遠小於或者是遠大於測試的圖片尺寸,另外為了便於觀察效果我給每個ImageView的背景都設定了顏色,接著運行大圖和小圖的測試結果,效果如下:

        通過上述實驗結果對比,就可以得出部分屬性的展示效果。比如fitXY屬性,當ImageView的屬性設定成了fitXY時,圖片的寬和高就會相應的展開或者是壓縮來填充滿整個ImageView,注意這種拉放縮不成比例。當ImageView的屬性設定成了matrix時,如果圖片寬高大於ImageView的寬高時,圖片的顯示就是從ImageView的左上方開始平鋪,超出部分不再顯示;如果圖片寬高小於ImageView的寬高時,圖片的顯示也是從ImageView的左上方開始平鋪,缺少部分空白顯示出來或者是顯示ImageView的背景。以上僅僅是根據運行結果來得出的結果,權威結論還要通過查看源碼來得出,本文分析的源碼是Android2.2版本。
        分析ImageView的源碼首先從它的構造方法開始,看一下構造方法裡邊都做了什麼工作。構造方法如下:

public ImageView(Context context) {  super(context);  initImageView(); }  public ImageView(Context context, AttributeSet attrs) {  this(context, attrs, 0); }  public ImageView(Context context, AttributeSet attrs, int defStyle) {  super(context, attrs, defStyle);  initImageView();   TypedArray a = context.obtainStyledAttributes(attrs,  com.android.internal.R.styleable.ImageView, defStyle, 0);   Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);  if (d != null) {  setImageDrawable(d);  }   //////////////////////////////////////////////////  //  // 以下源碼部分屬性初始化不涉及主核心,不再貼出  //  //////////////////////////////////////////////////    a.recycle();   //need inflate syntax/reader for matrix } 

        通過查看構造方法發現,在三個構造方法中都調用了initImageView()這個方法,這個方法是幹嘛使的,我們稍後在看。其次是根據在布局檔案中定義的屬性來初始化相關屬性,在測試布局中我們僅僅是用了ImageView的src,layout_width,layout_height和scaleType屬性(background屬性暫時忽略)。那也就是說在建構函式的初始化中就是對相關屬性進行了賦值操作,通過解析src屬性我們擷取到了一個Drawable的執行個體對象d,如果d是非空的話就把d作為參數又調用了setImageDrawable(d)函數,我們看看一下這個函數主要做了什麼工作,源碼如下:

/**  * Sets a drawable as the content of this ImageView.  *  * @param drawable The drawable to set  */ public void setImageDrawable(Drawable drawable) {  if (mDrawable != drawable) {  mResource = 0;  mUri = null;   int oldWidth = mDrawableWidth;  int oldHeight = mDrawableHeight;   updateDrawable(drawable);   if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {  requestLayout();  }  invalidate();  } } 

        此方法類型是public的,目的是幹嘛使的不再解釋了,注釋上說的是把給定的drawable作為當前ImageView的展示內容,接著是個條件判斷,在剛剛完成初始化的時候mDrawable屬性還沒有被賦值此時為空白,因此判斷成立程式進入條件陳述式繼續執行,在這裡把屬性mResource和mUri歸零操作並計入mDrawableWidth和mDrawableHeight的初始值,緊接著把傳遞進來的drawable參數傳遞給了updateDrawable()方法,那我們繼續跟進updateDrawable()看看這裡邊又做了什麼操作,源碼如下:

private void updateDrawable(Drawable d) {  if (mDrawable != null) {  mDrawable.setCallback(null);  unscheduleDrawable(mDrawable);  }  mDrawable = d;  if (d != null) {  d.setCallback(this);  if (d.isStateful()) {  d.setState(getDrawableState());  }  d.setLevel(mLevel);  mDrawableWidth = d.getIntrinsicWidth();  mDrawableHeight = d.getIntrinsicHeight();  applyColorMod();  configureBounds();  } else {  mDrawableWidth = mDrawableHeight = -1;  } } 

        該方法首先進行了非空判斷,此時mDrawable的值依然是空,所以條件判斷不成立跳過此部分,緊接著把傳遞進來的不允許為 NULL 的參數d的字賦值給了屬性mDrawable,到這裡mDrawable才算是完成了賦值操作。然後又進行了條件判斷,並設定d的callback為當前ImageView(因為ImageView的父類View實現了Drawable的Callback介面)接下來又把圖片的寬和高分別賦值給了mDrawableWidth和mDrawableHeight,緊接著又調用了applyColorMod()方法,當我們沒有給ImageView設定透明度或者是顏色過濾器時該方法不會執行。然後調用configureBounds()方法,此方法是我們今天要講的和ScaleType屬性息息相關的重點,不耽誤時間了趕緊瞅一下源碼吧,(*^__^*) 嘻嘻……

 private void configureBounds() {  if (mDrawable == null || !mHaveFrame) {  return;  }    int dwidth = mDrawableWidth;  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.  mDrawable.setBounds(0, 0, dwidth, dheight);    if (ScaleType.MATRIX == mScaleType) {  // Use the specified matrix as-is.  if (mMatrix.isIdentity()) {  mDrawMatrix = null;  } else {  mDrawMatrix = mMatrix;  }   ////////////////////////////////////////代碼塊三////////////////////////////////////////   } else if (fits) {  // The bitmap fits exactly, no transform needed.  mDrawMatrix = null;   ////////////////////////////////////////代碼塊四////////////////////////////////////////   } else if (ScaleType.CENTER == mScaleType) {  // Center bitmap in view, no scaling.  mDrawMatrix = mMatrix;  mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),   (int) ((vheight - dheight) * 0.5f + 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((int) (dx + 0.5f), (int) (dy + 0.5f));   ////////////////////////////////////////代碼塊六////////////////////////////////////////   } 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 = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);  dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);    mDrawMatrix.setScale(scale, scale);  mDrawMatrix.postTranslate(dx, dy);   ////////////////////////////////////////代碼塊七////////////////////////////////////////   } 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));   ////////////////////////////////////////代碼塊八////////////////////////////////////////   }  }  } 

        configureBoundd()函數比較長,為了方便分析源碼,我把該函數代碼分成了8小塊,每一個小塊都是一個單獨的邏輯。每一塊的詳解如下:
代碼塊一:
        該模組代碼首先做了一個條件判斷,如果當前mDrawable為空白或者是mHaveFrame為false則函數直接返回不再往下執行,由於後邊的邏輯主要是根據ScaleType屬性的類型來判斷圖片的展示方式,所以再後來這個函數肯定是能往下走的通的,由於篇幅的原因不再深入講解該函數的調用時機,我會在之後的文章中專門根據源碼講解一下Android系統下View的繪製流程,在之後的繪製流程中會提到configureBounds()的調用時機。該代碼塊的邏輯是擷取圖片的寬高儲存在dwidth,dheight中,然後又擷取到了ImageView的顯示圖片地區的寬高存放在vwidth和vheight中。然後定義了一個boolean類型的變數,該變數若為true就表示不需要對圖片進行放縮處理。
代碼塊二:
        該代碼塊的邏輯是當擷取到的圖片尺寸的寬高未知或者是ImageView的ScaleType屬性為FIT_XY時,將mDrawable的顯示邊界設定成控制項ImageView的顯示地區大小,並且把mDrawMatrix對象設定成null。需要說明的是setBounds方法用來設定Drawable的繪製地區的,最終在ImageView的onDraw方法中把mDrawable表示的圖片繪製到ImageView上。
代碼塊三:
        如果圖片寬高不為0並且ImageView的ScaleType屬性不是FIT_XY時,就會把mDrawable的繪製地區設定成圖片的原始大小。接著進行判斷ImageView的ScaleType屬性值是否是MATRIX,如果是MATRIX類型,就會把當前mMatrix賦值給mDrawMatrix。
代碼塊四:
        代碼塊四是一個if(fits)的判斷,如果fits的值為true,就表示圖片大小等於ImageView的大小,不需要對圖片進行放縮處理了。
代碼塊五:
       當mScaleType的類型為CENTER時,實際是將圖片進行移位操作,直接點說就是把圖片的中心點移動到ImageView的中心點,如果圖片的寬高大於ImageView的寬高此時只顯示ImageView所包含的部分,大於ImageView的部分不再顯示。
      【注意:CENTER屬性只對圖片進行移動操作而不會進行放縮操作】。
代碼塊六:
        代碼塊六是當mScaleType==CENTER_CROP時,進行了一個條件判斷:if(dwidth *vheight >vwidth *dheight),看到這句代碼的時候我並沒有理解其含義,然後我把這句代碼轉換了一下寫法:if(dwidth / vwidth > dheight / vheight),通過這種轉換寫法然後再看就比較明白了,主要是用來判斷寬高比的,就是說用來判斷是圖片的寬比較接近ImageView控制項的寬還是圖片的高比較接近ImageView控制項的高。如果是圖片的高比較接近ImageView的高,通過計算擷取需要放縮的scale的值,再計算出需要對圖片的寬進行移動的值,最後通過對mDrawMatrix屬性進行設定放縮和移動來達到控製圖片進行放縮和移動的效果,同樣的邏輯處理了當圖片的寬比較接近ImageView的寬的情況。從代碼可以總結CENTER_CROP屬性的特點是:對圖片的寬高進行放縮處理,使一邊達到ImageView控制項的寬高,另一邊進行進行移動置中顯示若超出則不再顯示。
代碼塊七:
        代碼塊七是當mScaleType==CENTER_INSIDE時,首先判斷圖片寬高是否小於ImageView寬高,如果圖片寬高小於ImageView的寬高,則scale=1.0f,也就是說不對圖片進行放縮處理而是直接移動圖片進行置中顯示,否則通過Math.min((float)vwidth / (float)dwidth, (float) vheight / (float)dheight);計算出需要對圖片進行的放縮值,然後放縮圖片寬高並對圖片移動置中顯示。從代碼可以總結CENTER_INSIDE的特點是:控製圖片尺寸,對圖片寬高進行壓縮處理,根據圖片和控制項的寬高比拿最大的一邊進行壓縮使之同控制項一邊相同,另一邊小於控制項。
代碼塊八:
        代碼塊八是對mScaleType為FIT_CENTER,FIT_START,FIT_END的情況下統一做了處理,先設定mTempSrc和mTempDst的邊界後,通過調用mDrawMatrix的setRectToRect()方法來對圖片進行放縮和移動操作,使圖片最大邊始終等於ImageView相應的邊。結合代碼和代碼測試結果可以得出如下結論:
        當圖片的高大於寬時:
1.當mScaleType == FIT_START時,對圖片進行等比放縮,使圖片的高與ImageView的高相等,移動圖片使之靠左對齊。
2.當mScaleType == FIT_CENTER時,對圖片進行等比放縮,使圖片的高與ImageView的高相等,移動圖片使之置中對齊。
3.當mScaleType == FIT_END時,對圖片進行等比放縮,使圖片的高與ImageView的高相等,移動圖片使之靠右對齊。
        當圖片的寬大於高時:
1.當mScaleType == FIT_START時,對圖片進行等比放縮,使圖片的寬與ImageView的寬相等,移動圖片使之上對齊。
2.當mScaleType == FIT_CENTER時,對圖片進行等比放縮,使圖片的寬與ImageView的寬相等,移動圖片使之置中對齊。
3.當mScaleType == FIT_END時,對圖片進行等比放縮,使圖片的寬與ImageView的寬相等,移動圖片使之下對齊。
        到這裡mScaleType的8種用根據法算是分析完了,現在稍做總結:
FIT_XY:對原圖寬高進行放縮,該放縮不保持原比例來填充滿ImageView。
MATRIX:不改變原圖大小從ImageView的左上方開始繪製,超過ImageView部分不再顯示。
CENTER:對原圖置中顯示,超過ImageView部分不再顯示。
CENTER_CROP:對原圖置中顯示後進行等比放縮處理,使原圖最小邊等於ImageView的相應邊。
CENTER_INSIDE:若原圖寬高小於ImageView寬高,這原圖不做處理置中顯示,否則按比例放縮原圖寬(高)是之等於ImageView的寬(高)。
FIT_START:對原圖按比例放縮使之等於ImageView的寬高,若原圖高大於寬則靠左對齊否則上對其。
FIT_CENTER:對原圖按比例放縮使之等於ImageView的寬高使之置中顯示。
FIT_END:對原圖按比例放縮使之等於ImageView的寬高,若原圖高大於寬則靠右對齊否則下對其。
        還記得在博文開始的時候說到在ImageView的構造方法中都調用了initImageView()方法嗎?他的源碼如下:

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

        可以看到,當我們沒有在布局檔案中使用scaleType屬性或者是沒有手動調用setScaleType方法時,那麼mScaleType的預設值就是FIT_CENTER。
        好了,有關ImageView的ScaleType的講解就算結束了,如有錯誤歡迎指正。以後如有其它屬性需要詳解,再做記錄吧。

原文地址:http://blog.csdn.net/llew2011/article/details/50855655

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.