Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android源碼系列之深入理解ImageView的ScaleType屬性

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

編輯:關於Android編程

做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的值依然是空,所以條件判斷不成立跳過此部分,緊接著把傳遞進來的非空參數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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved