ImageView詳解---進階之旅

對於Android中ImageView的ScaleType的使用屬性


當ImageView的大小與它內容的大小不一致時,就出現了一個問題,裏面的內容應該怎麼展示?放大、縮小、靠上、靠下、居中、居右…針對這個問題ImageView提供了ScaleType屬性來控制。
ImageView一共有8種縮放規則,有些規則特別容易搞混,在選擇時經常會懵圈。網上能搜索到解釋很多要麼模棱兩可要麼根本就是錯誤的,我這裏直接通過一個Demo展示一下各種ScaleType到底有什麼不同,最後再從源碼的層面上去分析爲什麼是這樣。

8種縮放規則

ImageView一共有:

  1. MATRIX
  2. FIT_XY
  3. FIT_START
  4. FIT_CENTER
  5. FIT_END
  6. CENTER
  7. CENTER_CROP
  8. CENTER_INSID

8種縮放規則。下面一個一個介紹, 不過MATRIX這種縮放方式放到最後再介紹(理論上它能實現任何你想要的縮放效果)。
爲了更清楚的對比各種ScaleType的區別,我這裏使用了三種比例大小不同的圖片,原圖如下:

3張圖片1張極長,1張極寬,1張寬高差不太多但極小。每張圖片在4個邊上都有紅色的邊框以方便對比圖片的縮放是否超出了邊界。


下面來一張屬性詳解圖,這樣的話會一目瞭然。

詳細解釋

FIT_XY

這種方式最好理解.

在X、Y方向上同時拉伸以填滿ImageView控件的大小。效果和把圖片直接設爲ImageView的background一樣。

真實效果如下圖所示:

圖片都完整顯示了(紅色邊框都顯示出來了,只不過在x,y方向上拉伸的幅度不一樣,所以會顯得X、Y方向上的邊框粗細不一樣)

FIT_START

這種方式用的應該不是很多,但網上關於這種方式的解釋基本都是錯誤的:FIT_START是置於頂部
首先這種解釋沒有說明:圖片內容到底會不會縮放。然後,說內容是置於頂部並不準確。完整且正確的解釋如下:

1. 圖片內容會縮放,長寬同時按比例縮放,直到其中一個方向上先撐滿ImageView控件。
2. 若在X方向上先撐滿,則圖片內容會居上顯示;若在Y方向上先撐滿則會在居左顯示。

真實效果如下圖所示:

FIT_CENTER

這種方式是ImageView的默認縮放方式,網上關於這種方式的解釋也是不準確的,正確的解釋如下:

1. 圖片內容會縮放,長寬同時按比例縮放,直到其中一個方向上先撐滿ImageView控件。
2. 縮放完畢後,圖片內容會被移動到ImageView的正中心(圖片的中心與ImageView的中心對齊)

真實效果如下圖所示:

FIT_END

和FIT_START一樣,網上關於這種方式的解釋也是錯誤的。正確的解釋如下:

1. 圖片內容會縮放,長寬同時按比例縮放,直到其中一個方向上先撐滿ImageView控件。
2. 若在X方向上先撐滿,則圖片內容會居下顯示;若在Y方向上先撐滿則會在居右顯示。

真實效果如下圖所示:

CENTER

這種方式很簡單,網上的解釋基本都沒什麼問題。完整的解釋如下:

1. 圖片內容不會縮放
2. 圖片內容居中顯示(圖片的中心與ImageView的中心對齊),若圖片過大會出現圖片周邊區域超出ImageView的範圍

真實效果如下圖所示:

CENTER_CROP

這種方式,從名字上並不能完全清楚它所有的規則,完整的解釋如下:

1. 圖片內容會縮放,長寬同時按比例縮放,直到最後一個方向上撐滿ImageView控件爲止。
2. 圖片內容居中顯示(圖片的中心與ImageView的中心對齊),圖片的周邊可能會被裁剪

真實效果如下圖所示:

可以看到,對於第1張圖片,其在X方向最後撐滿,後兩張圖片在Y方向上最後撐滿。周邊的區域被裁剪了。

CENTER_INSID

這種縮放方式很容易與默認的縮放方式(FIT_CENTER)搞混,這是因爲除非你的圖片足夠小,否則你看不出它們的區別。完整的解釋如下:

1. 圖片內容不一定會縮放,只有在圖片長或寬大於ImageView控件的長或寬時纔會縮放(而且是縮小)
2. 縮放完成後,圖片會居中顯示

真實效果如下圖所示:

可以看到第2張小圖並未縮放。

MATRIX

上面7種縮放方式是系統內置的,若上面這些縮放方式並不能滿足你的需求,MATRIX這種方式就派上用場了。簡單說來這種縮放類型,支持你自定義圖片的縮放的規則。

假設你有這樣一種縮放需求:圖片在X軸方向上撐滿ImageView控件。在Y軸方向上置於頂部。效果上如下:

而這張圖的原圖是這樣的:

至於Y軸方向上的底部可能被圖片填充,也可能不被,這就看圖片夠不夠高了。
那麼如何實現這種效果呢?

1. 第1步把ImageView的ScaleType設置爲MATRIX
2. 自定義一個ImageView,重寫其中的setFrame方法

關鍵代碼如下:

  1. 首先,我們計算出寬度撐滿需要的縮放比例:
  2. 1
    
    float widthScaleFactor = w / cw;
    
  1. 然後對matrix進行設置,改變其Scale值。
  1. 1
  1. matrix.setScale(widthScaleFactor, widthScaleFactor, 0, 0);

Android Scaletype源碼分析及自定義Matrix縮放規則

上一篇中,我詳細的總結了ImageView各種ScaleType對應的縮放效果。對於Matrix這種強大的縮放方式並沒有詳細介紹,在這一篇中會主要說一下這種縮放方式。

讓我們先從源碼中看一下上一篇中介紹的幾種縮放方式都是怎麼實現的,然後再通過Matrix的方式來實現一下上一篇最後沒有實現的3種自定義縮放規則(寬度撐滿並居下、高度撐滿居左、高度撐滿居右)

源碼分析

打開ImageView的源碼,找到如下方法:

1
2
3
4
5
6
7
@Override
protected boolean setFrame(int l, int t, int r, int b) {
    final boolean changed = super.setFrame(l, t, r, b);
    mHaveFrame = true;
    configureBounds();
    return changed;
}

其中有一個configureBounds()方法。我們需要的代碼就在這個方法裏面。首先看一下FIT_XY這種方式是如何實現的

1. FIT_XY源碼實現

1
2
3
4
5
6
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;
  }

上述代碼中的vwidth與vheight是ImageView控件大小減去padding後剩下的值即可容納內容的最大空間。可以看到這種縮放方式非常簡單粗暴,直接通過改變Drawable的繪製區域來達到,並未進行任何的Matrix操作。

2. FIT_START/FIT_CENTER/FIT_END源碼實現

之所以把它們3個的放在一起說,是因爲它們的實現邏輯是一樣,在configureBounds()方法中有如下代碼如下:

1
2
3
4
5
// Generate the required transform.
 mTempSrc.set(0, 0, dwidth, dheight);
 mTempDst.set(0, 0, vwidth, vheight);
 mDrawMatrix = mMatrix;
 mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));

可以看到這裏用到了setRectToRect方法,這個方法可以根據變換前後的兩個矩形得到對應的變換矩陣。上面代碼中mTempSrc即是內容矩形大小也是變換的源矩形大小,mTempDst是ImageView控件矩形大小也是目標矩形大小。
這個方法還有一個參數 Matrix.ScaleToFit 它用來指定縮放選項,一共有4個選項:

  1. FILL: 獨立的縮放X,Y,所以這個選項可能會改變圖片的長寬比(其實效果與FIT_XY一樣)
  2. START: 這種縮放方式的效果對應ImageView的FIT_START
  3. CENTER: 這種縮放方式的效果對應ImageView的FIT_CENTER
  4. END: 這種縮放方式的效果對應ImageView的FIT_END

也即是ImageView的這3種縮放規則直接使用的Matrix內置的這幾種變換規則實現。你也可以自己用如下代碼驗證一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
	protected boolean setFrame(int l, int t, int r, int b) {
		boolean changed = super.setFrame(l, t, r, b);
		test();
		return changed;
	}

	private void test() {
		final int dw= getDrawable().getIntrinsicWidth();
		final int dh= getDrawable().getIntrinsicHeight();

		final float vw= getWidth() - getPaddingLeft() - getPaddingRight();
		final float vh= getHeight() - getPaddingTop() - getPaddingBottom();

		mTempSrc.set(0, 0, dw, dh);
		mTempDst.set(0, 0, vw, vh);
		Log.e("ghui", "mTempSrc: " + mTempSrc.toShortString());
		Log.e("ghui", "mTempDst: " + mTempDst.toShortString());
		Matrix matrix = getImageMatrix();
		matrix.reset();
		Log.e("ghui", "before: " + matrix.toShortString());
		matrix.setRectToRect(mTempSrc, mTempDst, Matrix.ScaleToFit.START);
		Log.e("ghui", "after: " + matrix.toShortString());
		setImageMatrix(matrix);
	}

這裏不再贅述。

3.CENTER

CENTER的源碼實現對應如下代碼:

1
2
3
4
5
6
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));
}

可以看到這裏是直接通過改變Matrix的Translate將內容從左上角移動到了控件的中心,使內容的中心與控件的中心對齊。並未做任何的縮放操作,
和我們上一篇中總結的效果一致。

4.CENTER_CROP

這種方式的對應的源碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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(Math.round(dx), Math.round(dy));
}

這裏會首先比較內容的寬高比與控件的寬高比的大小,若內容的寬高比更大(內容更寬)則以控件高度與內容高度的比作爲縮放比例。反之則會將控件寬度與內容寬度的比作爲縮放的比例。這樣實現的效果即是將內容中更小的一者(寬或高)縮放到與控件對應的一方一樣。也與上一篇中總結的效果一樣。
接着會通過postTranslate方法將內容移動到控件中心,計算方法與CENTER中的一樣。

5. CENTER_INSID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}

可以看到若內容的寬高都小於控件的寬高,這種方式的Scale爲1,即不會去縮放。否則的話則會去選擇一個更小的縮放比例(控件的寬高與內容的寬高比,取其中較小的一個 )
效果也與上一篇中總結的一致。

Matrix-實現自定義的縮放效果。

其實通過學習上面的源碼,自定義實現 寬度撐滿並居上、寬度撐滿並居下、高度撐滿居左、高度撐滿居右這幾種效果也是很簡單的。先把代碼貼出來:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
	protected boolean setFrame(int l, int t, int r, int b) {
		boolean changed = super.setFrame(l, t, r, b);
		if (getScaleType() == ScaleType.MATRIX) transformMatrix();
		return changed;
	}

	private void transformMatrix() {
		Matrix matrix = getImageMatrix();
		matrix.reset();
		float h = getHeight();
		float w = getWidth();
		float ch = getDrawable().getIntrinsicHeight();
		float cw = getDrawable().getIntrinsicWidth();
		float widthScaleFactor = w / cw;
		float heightScaleFactor = h / ch;
		if (alignType == AlignType.LEFT) {
			matrix.postScale(heightScaleFactor, heightScaleFactor, 0, 0);
		} else if (alignType == AlignType.RIGHT) {
			matrix.postTranslate(w - cw, 0);
			matrix.postScale(heightScaleFactor, heightScaleFactor, w, 0);
		} else if (alignType == AlignType.BOTTOM) {
			matrix.postTranslate(0, h - ch);
			matrix.postScale(widthScaleFactor, widthScaleFactor, 0, h);
		} else { //default is top
			matrix.postScale(widthScaleFactor, widthScaleFactor, 0, 0);
		}
		setImageMatrix(matrix);
	}

	public enum AlignType {
		LEFT(0),
		TOP(1),
		RIGHT(2),
		BOTTOM(3);
		AlignType(int ni) {
			nativeInt = ni;
		}
		final int nativeInt;
	}

首先看第一種縮放方式:
1.高度撐滿居左
因爲ImageView的內容默認會居左上,所以這裏可以直接對matrix執行縮放操作,將高度縮放到撐滿控件。如下圖所示:

2.高度撐滿居右
因爲要居右,所以首先將內容移動到右上角,然後再以右上角爲中心執行縮放操作。如下圖所示:

3.寬度撐滿並居下
因爲要居下,所以首先將內容移動到左下角,然後再以左下角爲中心執行縮放操作。

4.寬度撐滿並居上
這種方式更簡單,因爲內容默認在左上角,所以這裏可以直接執行縮放操作即可。


  1. 
    
    
    

 

發佈了20 篇原創文章 · 獲贊 24 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章