一谦炒、概述
在使用ImageView
的過程當中积暖,經(jīng)常需要通過scaleType
來對原始的圖像進行處理,使得它能在空間中合理地展示孙咪。
二堪唐、scaleType
的分類
首先,我們簡單介紹一下scaleType
的分類:
2.1 通過Matrix
設置
這種情況下翎蹈,對應的模式只有一種:
ScaleType.MATRIX
最終淮菠,在這種情況下,我們可以同setImageMatrix(Matrix matrix)
來改變荤堪。
2.2 填充類型
這一類屬性的特點就是通過拉伸或者壓縮圖片合陵,使得原圖片中所有元素都能夠展現(xiàn),并且至少填滿控件x,y
軸的其中一個澄阳。
一共有四類:
-
ScaleType.FIX_XY
:不考慮原圖的比例拥知,拉伸或者壓縮使得它等于控件的寬高。
下面三種都會維持原圖的比例碎赢,使得它們的x,y
都小于等于控件的寬高低剔,只是最終的圖形放的位置不同。
-
ScaleType.FIT_START
:放置在左上角肮塞。 -
ScaleType.FIT_CENTER
:放置在中間襟齿。 -
ScaleType.FIT_END
:放置在右下角。
2.3 中心重合類型
下面的三種類型都會使得控件的中心和圖片中心重合:
ScaleType.CENTER
要求一點:原圖的中心和控件的中心重合
ScaleType.CENTER_CROP
要求三點:整個控件能夠被填滿
原圖的比例不變
原圖的中心和控件的中心重合峦嗤。
-
保證原圖的
x,y
軸上的元素至少有一個在控件中能被完全展示,那么有一下兩種情況屋摔,在下面的操作做完之后烁设,裁剪掉多余的部分:- 如果原圖沒有填滿控件,那么會慢慢按比例放大钓试,直到填滿控件装黑;
- 如果原圖已經(jīng)填滿控件,那么它會慢慢縮小弓熏,直到某一邊和控件重合恋谭。
ScaleType.CENTER_INSIDE
要求三點:原圖的所有像素位于控件內(nèi)部
原圖的比例不變
圖片的中心和控件的中心重合。
它不要求原始圖片填滿x,y
軸的任意一個挽鞠,因此疚颊,如果原圖的長寬都小于等于控件的長寬狈孔,不會進行放大操作,這也是它和ScaleType.FIT_CENTER
的區(qū)別材义。
三均抽、示例
下面,我們通過一個簡單的Demo
來展示一下各種類型的具體表現(xiàn)其掂,我們有兩個大小一樣的ImageView
和兩個大小不同的原圖油挥,其中左邊的ImageView
要比原圖小,右邊的ImageView
要比原圖大款熬。
-
ScaleType.FIX_XY
:
-
ScaleType.FIT_START
:
-
ScaleType.FIT_CENTER
-
ScaleType.FIT_END
-
ScaleType.CENTER
-
ScaleType.CENTER_CROP
-
ScaleType.CENTER_INSIDE
四深寥、源碼分析
4.1 給ImageVIew
設置src
的接口
在ImageView
當中,設置圖片的接口主要有下面幾個函數(shù):
public void setImageBitmap(Bitmap bm)
public void setImageResource(@DrawableRes int resId)
public void setImageURI(@Nullable Uri uri)
public void setImageDrawable(@Nullable Drawable drawable)
4.2 setImageBitmap
的流程
我們就以平時常用的setImageBitmap
為例贤牛,分析一下它整個的流程:
- 第一步:當我們調(diào)用
setImageBitmap
之后惋鹅,它會把Bitmap
封裝在BitmapDrawable
當中,之后調(diào)用了setImageDrawable(Drawable drawable)
方法:
public void setImageBitmap(Bitmap bm) {
mDrawable = null;
if (mRecycleableBitmapDrawable == null) {
mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
} else {
mRecycleableBitmapDrawable.setBitmap(bm);
}
setImageDrawable(mRecycleableBitmapDrawable);
}
- 第二步:調(diào)用
setImageDrawable
public void setImageDrawable(@Nullable Drawable drawable) {
//如果不是同一個資源.
if (mDrawable != drawable) {
mResource = 0;
mUri = null;
//舊的寬高.
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
//關(guān)鍵方法
updateDrawable(drawable);
//如果寬高不同盔夜,才請求重新布局.
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
//只要替換了資源就需要重新繪制.
invalidate();
}
}
上面的關(guān)鍵方法在updateDrawable
當中:
private void updateDrawable(Drawable d) {
//....
if (d != null) {
//...
//這里面根據(jù)scaleType配置mDrawMatrix.
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}
4.3 configureBounds
改變Matrix
在configureBounds
里就會根據(jù)我們所配置的scaleType
來決定mDrawable
如何顯示负饲,在這里面有一個重要的變量mDrawMatrix
,我們前面說到的所有變換都是通過它來實現(xiàn)的喂链,當然返十,我們除了可以讓系統(tǒng)自己根據(jù)scaleType
來生成matrix
,也可以通過setImageMatrix
手動的指定自己的變換:
private void configureBounds() {
if (mDrawable == null || !mHaveFrame) {
return;
}
//1.得到原始資源的寬高.
final int dwidth = mDrawableWidth;
final int dheight = mDrawableHeight;
//2.得到控件的寬高椭微,這里去掉了控件的padding.
final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
//3.表示原始資源已經(jīng)能夠填滿控件.
final boolean fits = (dwidth < 0 || vwidth == dwidth)
&& (dheight < 0 || vheight == dheight);
//4.假如有一邊是wrap_content洞坑,或者是FIX_XY,那么填滿整個控件.
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
} else {
mDrawable.setBounds(0, 0, dwidth, dheight);
//如果scaleType是matrix.
if (ScaleType.MATRIX == mScaleType) {
//單位矩陣的情況蝇率,設為null.
if (mMatrix.isIdentity()) {
mDrawMatrix = null;
} else {
//否則最后的DrawMatrix就是我們傳入的Matrix.
mDrawMatrix = mMatrix;
}
} else if (fits) {
//如果原始資源已經(jīng)填滿控件迟杂,那么不需要考慮其它的變換了.
mDrawMatrix = null;
} else if (ScaleType.CENTER == mScaleType) {
//當scaleType為center的時候.
mDrawMatrix = mMatrix;
//移動到中心,初始時候本慕,控件和原始資源的(0,0)點是重合的.
mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f), Math.round((vheight - dheight) * 0.5f));
} else if (ScaleType.CENTER_CROP == mScaleType) {
mDrawMatrix = mMatrix;
//當scaleType是centerCrop的時候.
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));
} else if (ScaleType.CENTER_INSIDE == mScaleType) {
//當scaleType是centetInside時
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);
} else {
//設置兩個區(qū)域的大小.
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);
mDrawMatrix = mMatrix;
//這里處理FIX_START,FIX_END,FIX_CENTER的情況.
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
}
}
}
4.4 onDraw
中進行繪制
那么這個mDrawMatrix
是在什么時候使用的呢,我們看一下onDraw
方法:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
mDrawable.draw(canvas);
} else {
final int saveCount = canvas.getSaveCount();
//創(chuàng)建一個新的圖層.
canvas.save();
if (mCropToPadding) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
scrollX + mRight - mLeft - mPaddingRight,
scrollY + mBottom - mTop - mPaddingBottom);
}
//移動到去掉padding的左上角.
canvas.translate(mPaddingLeft, mPaddingTop);
//根據(jù)mDrawMatrix進行變換.
if (mDrawMatrix != null) {
canvas.concat(mDrawMatrix);
}
//再在這個變換上面繪制我們的資源.
mDrawable.draw(canvas);
//把合成完成的圖片繪制上去.
canvas.restoreToCount(saveCount);
}
}
4.5 小結(jié)
我們總結(jié)一下藤违,整個scaleType
的原理就是在configureBounds
中配置了mDrawMatrix
浪腐,而在onDraw
當中會根據(jù)mDrawMatrix
來對圖層進行變換,在這個變換之后的圖層上進行繪制mDrawable
顿乒,之后再恢復圖層议街。
五、ImageView
的src
和background
的區(qū)別
上面璧榄,我們看到的都是src
設置的效果特漩,我們回憶一下吧雹,通過設置android:background
也可以設置一個圖片給它,其實background
是View
的屬性拾稳,在我們之前分析View
的繪制流程的時候吮炕,draw(canvas)
中有一步就是繪制背景:
private void drawBackground(Canvas canvas) {
//1.設置背景的邊界.
setBackgroundBounds();
//2.如果有滾動,那么背景需要相應的滾動.
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
//3.繪制背景.
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
我們來看一下設置背景的邊界的函數(shù)访得,可以看到龙亲,這里沒有考慮padding
值,也就是說我們通過background
設置的圖片是填滿整個控件悍抑,并且不考慮padding
的:
void setBackgroundBounds() {
if (mBackgroundSizeChanged && mBackground != null) {
//沒有考慮padding部分.
mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
}
最后再結(jié)合一下第四節(jié)的知識鳄炉,我們是先繪制背景,然后才在ImageView
的onDraw
函數(shù)當中在canvas
上繪制的搜骡,因此轻腺,src
的圖片一定會繪制在backgroud
之上辩尊。