標籤:
關於ScaleType,網上介紹這個枚舉對象的文章很多了,不過基本都只是介紹了它的效果。我在做可縮放移動的ImageView時,為了實現圖片的縮放和拖動,需要記錄圖片的原始Matrix,在使用過程中發現,這個原始Matrix和ScaleType有著直接的關係,不同的ScaleType將會直接影響到Matrix的值進而影響了該自訂控制項的效果。為了更好地理清兩者的關係,我去閱讀了ImageView的源碼,在此記錄整理後的個人理解。
首先簡單介紹下不同的ScaleType,其實看名字就知道,Scale(比例)Type(類型),這個對象用以調整圖片的比例縮放類型。不同的ScaleType影響的就是圖片長與寬的不同縮放比例。
matrix 用矩陣來繪製(從左上方起始的矩陣地區) fitXY 把圖片不按比例擴大/縮小到View的大小顯示(確保圖片會完整顯示,並充滿View) fitStart 把圖片按比例擴大/縮小到View的寬度,顯示在View的上部分位置(圖片會完整顯示) fitCenter 把圖片按比例擴大/縮小到View的寬度,置中顯示(圖片會完整顯示) fitEnd 把圖片按比例擴大/縮小到View的寬度,顯示在View的下部分位置(圖片會完整顯示)
center 按圖片的原來size置中顯示,當圖片寬超過View的寬,則截取圖片的置中部分顯示,當圖片寬小於View的寬,則圖片置中顯示
centerCrop 按比例擴大/縮小圖片的size置中顯示,使得圖片的高等於View的高,使得圖片寬等於或大於View的寬
centerInside 將圖片的內容完整置中顯示,使得圖片按比例縮小或原來的大小(圖片比View小時)使得圖片寬等於或小於View的寬 (圖片會完整顯示) 我們知道,可以使用getImageMatrix方法擷取ImageView中圖片的Matrix對象,事實上,ScaleType影響的也正是該Matrix對象。在ImageView源碼中,configureBounds用以處理Matrix和ScaleType的關係。接下去通過閱讀源碼來解釋下為什麼會實現上述顯示效果。 configureBounds源碼如下:
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)); } } }
分段閱讀:
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);
1.在這一步中,擷取了圖片對象(即Drawable對象,ImageView實際顯示的是Drawable對象)的寬度和高度,存放在dwidth和dheight中。同時,擷取了ImageView控制項的顯示地區的寬度和高度,存放在vwidth和vheight中。
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 {
2.當圖片的大小未知或者ScaleType為FIT_XY時,將drawable的邊界設定為控制項的大小,並且將圖片matrix對象設定為null。setBounds方法用以設定Drawable繪製地區的大小,是一個矩形對象。在ImageView的onDraw方法中,最終會將Drawable表示的圖片繪製到該地區上。在這裡,就是將圖片繪製到ImageView大小的地區上,也就是所謂的圖片完整顯示,填充滿ImageView。注意此處並未對Matrix對象進行操作,而是直接設定為null,所以當ImageView的ScaleType為FIT_XY時,getImageMatrix將擷取到一個初始Matrix對象,getImageMatrix方法如下。
public Matrix getImageMatrix() { if (mDrawMatrix == null) { return new Matrix(Matrix.IDENTITY_MATRIX); } return mDrawMatrix; }
else { // We need to do the scaling ourself, so have the drawable // use its native size. mDrawable.setBounds(0, 0, dwidth, dheight);
3.當ScaleType不為FIT_XY並且圖片寬高不為0,就會把drawable的繪製地區設成圖片實際大小。註:此處的實際大小不是指檔案大小,而是指源檔案大小和螢幕解析度計算後的結果。
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; }
4.接下去判斷ScaleType是否為MATRIX,如果為MATRIX類型,就會把當前繪製用的的Matrix賦值為mMatrix,mMatrix是通過setImageMatrix方法賦值的。這之後還有一個if(fits)判斷,假如fits為true,表示圖片大小等於ImageView大小,也不需要進行縮放操作了。
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)); }
5.當ScaleType類型為CENTER時,實際操作是將圖片進行右移和下移操作,移動的數值分別為為ImageView寬度、高度與圖片寬度、高度差值的一半,這樣就達到了置中顯示的效果。注意,CENTER不進行縮放操作只進行位移操作,所以圖片的顯示大小並未改變,當圖片大於控制項時,只顯示置中部分,大於控制項的部分未顯示。
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)); }
6.在CENTER_CROP中,首先對dwidth * vheight > vwidth * dheight進行了一個判斷,這個判斷是什麼意思呢?其實換成這樣比較容易理解:dwidth/vwidth>dheight/vheight,即判斷到底是圖片的寬度比較接近控制項寬度還是圖片高度比較控制項高度,最終會取相差較大的項,將其放大至控制項對應的值。而另外一項將超出控制項大小。之後,對其進行位移,使超出控制項大小的一項置中顯示。註:當圖片大於控制項時,是將超出更少的項進行縮放。CROP的目的在於對圖片寬高進行變換,使其中一項和控制項的值相等,另外一項大於控制項的值。
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); }
7.當為CENTER_INSIDE時,若圖片高寬均小於控制項高寬,則不進行縮放只進行位移,位移方式跟其他情況相同。否則,將根據圖片和控制項的寬高比例差距更大的一項進行縮放,縮放的結果就是其中一項和控制項相同,另外一項小於控制項值。這個剛好和CENTER_CROP相反。
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)); }
8.最後三種FIT_CENTER、FIT_START、FIT_END都是調用setRectToRect擷取縮放位移矩陣。setRectTorect返回一個Matrix,該Matrix表示從矩形mTempSrc到mTemDst的變換矩陣,根據第三個參數Matrix.ScaleToFit來確定縮放選項。
Matrix.ScaleToFit定義了四種選項:
CENTER: 保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。
END:保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。END提供右下對齊。
FILL: 可能會變換矩形的長寬比,保證變換和目標矩陣長寬一致。
START:保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。START提供左上對齊。
ScaleType的FIT_CENTER、FIT_START、FIT_END分別對應於這裡的CENTER、END、START。
總結下就一句話:ScaleType本質上是影響了ImageView中的mDrawMatrix對象,該對象用以在繪製時對Drawable對象進行矩陣轉換。
Android:ScaleType與Matrix相關