最近在研究圖片加載庫,趁機(jī)找時(shí)間學(xué)習(xí)下ImageView的源碼它褪,探究下它內(nèi)部機(jī)制,同時(shí)學(xué)習(xí)下View的封裝思想荣恐。
概述
Displays an arbitrary image, such as an icon. The ImageView class can load images from various sources (such as resources or content providers), takes care of computing its measurement from the image so that it can be used in any layout manager, and provides various display options such as scaling and tinting.
ImageView可以顯示各種圖片火惊,比如icon圖求类。它可以顯示各種來源的圖片,包括Resource屹耐,Content Provider等尸疆,顯示的過程中會(huì)計(jì)算圖片的尺寸從而適應(yīng)任何布局管理器,并且提供各種顯示可選項(xiàng)张症,比如縮放仓技、著色等鸵贬。(來自筆者蹩腳的翻譯)
XML屬性
我們平時(shí)一般在XML聲明ImageView俗他,除了繼承自View的屬性,官方文檔列出了以下屬性阔逼,我們也主要從這些屬性了解ImageView的功能
XML屬性 | 說明 |
---|---|
android:adjustViewBounds | 設(shè)置為true兆衅,可以讓ImageView根據(jù)圖片的寬寬比調(diào)整大小,使用該屬性時(shí)不能固定ImageView的寬高嗜浮,或者將寬高設(shè)置為match_parent |
android:baseline | 設(shè)置文字的baseLine到ImageView頂部的距離羡亩,如果baselineAlignBottom被設(shè)置為true,則該屬性會(huì)被覆蓋危融,即該屬性無效 |
android:baselineAlignBottom | 設(shè)置為true畏铆,baseLine則為ImageView的底部 |
android:cropToPadding | 設(shè)置為true,圖像會(huì)在Padding屬性的基礎(chǔ)上進(jìn)行裁剪 |
android:maxHeight | 設(shè)置ImageView的最大高度 |
android:maxWidth | 設(shè)置ImageView的最大寬度 |
android:scaleType | 設(shè)置ImageView的縮放模式 |
android:src | 設(shè)置ImageView顯示圖片資源 |
android:tint | 設(shè)置ImageView顯示圖片的著色顏色 |
android:tintMode | 設(shè)置ImageView顯示圖片的著色模式 |
源碼學(xué)習(xí)
接下來通過查看ImageView源碼吉殃,分析下ImageView上面這些屬性是怎么起效果的辞居,像adjustViewBound
、cropToPadding
這些屬性經(jīng)常用不太順蛋勺,從源碼的角度了解這些屬性是怎么影響ImageView最終效果瓦灶。
圖片顯示
ImageView最大的功能就是顯示圖片,首先研究下圖片是怎么在ImageView中顯示的抱完。
在平時(shí)使用中贼陶,我們可以通過以下兩種方式設(shè)置圖片:
- XML 設(shè)置src屬性,可以設(shè)置drawable資源巧娱,eg.
android:src="@drawable/test"
- Java代碼設(shè)置碉怔,可以設(shè)置調(diào)用imageView的
setImageBitmap
,setImageDrawable
,setImageResource
,setImageURI
設(shè)置
注意如果使用 setImageResource
, setImageURI
官方提示這兩個(gè)方法是需要在UI線程中解析圖片資源的,建議不要使用這兩個(gè)方法加載太大的圖禁添,或者在其他線程中解析成Bitmap或者Drawable撮胧,避免畫面卡頓。
This does Bitmap reading and decoding on the UI thread, which can cause a latency hiccup. If that's a concern, consider using
接下來看一下在ImageView內(nèi)部如何處理上荡。
在構(gòu)造函數(shù)中趴樱,XML中的src屬性會(huì)被讀取并轉(zhuǎn)化成Drawable馒闷,并通過setDrawable
設(shè)置圖片。
Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
if (d != null) {
setImageDrawable(d);
}
接著看下setDrawable
代碼叁征,判斷當(dāng)前的Drawable與設(shè)置的Drawable是否不同纳账,不同的話,把ImageView的成員變量mResource
和mUri
值置空捺疼,避免其它方式的值影響內(nèi)容顯示疏虫。然后調(diào)用updateDrawable
這個(gè)更新Drawable的方法。如果圖片寬高不同啤呼,需要重新布局requestLayout
卧秘,最后通過invalidate
刷新頁面,因?yàn)樽詈髨D片的顯示是通過onDraw
刷新官扣。
public void setImageDrawable(Drawable drawable) {
if (mDrawable != drawable) {
mResource = 0;
mUri = null;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(drawable);
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
updateDrawable
主要功能是將ImageView
中mDrawable
中替換翅敌,同時(shí)修改mDrawable
屬性,這里大部分是Drawable
的知識(shí)惕蹄,先不細(xì)究了蚯涮,留意下最后三個(gè)方法applyImageTint applyColorMod configureBounds
會(huì)影響圖片著色和縮放,后面會(huì)繼續(xù)提到卖陵。
private void updateDrawable(Drawable d) {
if (mDrawable != null) {
mDrawable.setCallback(null);
unscheduleDrawable(mDrawable);
}
mDrawable = d;
if (d != null) {
d.setCallback(this);
d.setLayoutDirection(getLayoutDirection());
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setVisible(getVisibility() == VISIBLE, true);
d.setLevel(mLevel);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyImageTint();
applyColorMod();
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}
到這里我們可以了解到通過XML設(shè)置屬性就是最終是修改了ImageView中的mDrawable
變量遭顶,同時(shí)setDrawable
方法也是通過updateDrawable
來更新mDrawable
。
接下來看一下ImageView
的幾個(gè)設(shè)置顯示圖片的方法泪蔫。
setImageBitmap
也是調(diào)用了setImageDrawable
棒旗,同時(shí)使用BitmapDrawable
可以減少中間過程中產(chǎn)生的drawable對(duì)象,可以大膽推測(cè)其他的Drawable
最終會(huì)轉(zhuǎn)化成BitmapDrawable
用于顯示撩荣。
public void setImageBitmap(Bitmap bm) {
setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
}
然后看下setImageResource
和setImageUri
兩個(gè)方法的思路是是一樣铣揉,首先判斷mResource
或者mUri
有值,然后將原有的mDrawable
置空婿滓,resolverUri
重新解析資源老速,這個(gè)方法最終也是調(diào)用了updateDrawable
,更新圖片顯示凸主,同樣也需要考慮到重新布局的問題橘券。
public void setImageResource(int resId) {
if (mUri != null || mResource != resId) {
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(null);
mResource = resId;
mUri = null;
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
public void setImageURI(Uri uri) {
if (mResource != 0 ||
(mUri != uri &&
(uri == null || mUri == null || !uri.equals(mUri)))) {
updateDrawable(null);
mResource = 0;
mUri = uri;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
圖片縮放 ScaleType
詳細(xì)大家都了解過ImageView
的圖片縮放類型,現(xiàn)在看一下是怎么通過代碼實(shí)現(xiàn)的卿吐。
首先看setScaleType
發(fā)現(xiàn)好像沒有什么特別旁舰,是重新設(shè)置ScaleType
枚舉,然后重新布局與繪制嗡官。
public void setScaleType(ScaleType scaleType) {
if (scaleType == null) {
throw new NullPointerException();
}
if (mScaleType != scaleType) {
mScaleType = scaleType;
setWillNotCacheDrawing(mScaleType == ScaleType.CENTER);
requestLayout();
invalidate();
}
}
Ctrl+F
搜索 ScaleType
箭窜,仔細(xì)一看,原來藏在configureBounds
里衍腥,這里對(duì)ScaleType
枚舉進(jìn)行處理磺樱,這個(gè)方法我們剛才已經(jīng)在updateDrawable
中看到過纳猫,
接下來看下代碼。
private void configureBounds() {
if (mDrawable == null || !mHaveFrame) {
return;
}
//期望顯示的寬高竹捉,即要顯示的Drawable的寬高
int dwidth = mDrawableWidth;
int dheight = mDrawableHeight;
//ImageView中顯示內(nèi)容的寬與高(不包括內(nèi)邊距)
int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
int vheight = getHeight() - mPaddingTop - mPaddingBottom;
//判斷ImageView的顯示內(nèi)容寬高是否與期望顯示的寬高一致
boolean fits = (dwidth < 0 || vwidth == dwidth) &&
(dheight < 0 || vheight == dheight);
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
/* 如果Drawable沒有明確的尺寸或者設(shè)定為FIX_XY芜辕,那么圖片內(nèi)容會(huì)充滿整個(gè)ImageView
*/
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
} else {
//否則我們需要自己處理縮放,讓Drawable顯示原有尺寸
mDrawable.setBounds(0, 0, dwidth, dheight);
if (ScaleType.MATRIX == mScaleType) {
//判斷mMatric是否為單位矩陣块差,如果不是的話將mMatric賦值給mDrawMatric,用于onDraw中對(duì)圖片進(jìn)行變換
if (mMatrix.isIdentity()) {
mDrawMatrix = null;
} else {
mDrawMatrix = mMatrix;
}
} else if (fits) {
//如果剛好合適則不需要變換
mDrawMatrix = null;
} else if (ScaleType.CENTER == mScaleType) {
//如果ScaleType為CENTER侵续,對(duì)圖片不進(jìn)行縮放,將圖片移動(dòng)到ImageView中心
mDrawMatrix = mMatrix;
mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
(int) ((vheight - dheight) * 0.5f + 0.5f));
} else if (ScaleType.CENTER_CROP == mScaleType) {
//如果ScaleType為CENTER_CROP 憨闰,對(duì)圖片進(jìn)行等比縮放至圖片寬高大于等于ImageView状蜗,將圖片中心移動(dòng)到ImageView中心
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) {
//如果ScaleType為CENTER_INSIDE ,對(duì)圖片進(jìn)行等比縮放至圖片寬高小等于ImageView鹉动,將圖片中心移動(dòng)到ImageView中心
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 {
//剩下三種情況生成相應(yīng)的轉(zhuǎn)化矩陣
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);
mDrawMatrix = mMatrix;
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
}
}
}
通過configureBounds
中可以知道ScaleType
本質(zhì)就是通過Matrix
矩陣變換,主要通過縮放和位移實(shí)現(xiàn)具體效果轧坎。
ImageView構(gòu)造函數(shù)中初始化時(shí)將ScaleType
默認(rèn)為FIT_CENTER
。
mScaleType = ScaleType.FIT_CENTER;
cropToPadding 屬性
CropToPadding
這個(gè)屬性感覺平時(shí)不太用训裆,嘗試用了下又感覺沒什么效果眶根,所以這個(gè)屬性究竟是怎么其效果的呢?
首先我們可以找到mCropToPadding
這個(gè)變量边琉,我們平時(shí)修改的就是這個(gè)值,進(jìn)一步查找记劝,可以發(fā)現(xiàn)在onDraw
中有用到這個(gè)變量变姨,所以這個(gè)變量是在圖形繪制過程中生效的。我們來看下onDraw
中的相關(guān)代碼厌丑。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawable == null) {
return;
}
if (mDrawableWidth == 0 || mDrawableHeight == 0) {
return;
}
//如果變換矩陣為空定欧,并且沒有上內(nèi)邊距和左內(nèi)邊距則只直接繪制
if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
mDrawable.draw(canvas);
} else {
int saveCount = canvas.getSaveCount();
canvas.save();
//如果mCropToPadding設(shè)置為ture,需要裁剪區(qū)域怒竿,裁剪區(qū)域要考慮到x和y方向的內(nèi)容滾動(dòng)位移
if (mCropToPadding) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
scrollX + mRight - mLeft - mPaddingRight,
scrollY + mBottom - mTop - mPaddingBottom);
}
//根據(jù)上內(nèi)邊距和左內(nèi)邊距確定內(nèi)容繪制起點(diǎn)
canvas.translate(mPaddingLeft, mPaddingTop);
//如果存在變換矩陣則進(jìn)行變換
if (mDrawMatrix != null) {
canvas.concat(mDrawMatrix);
}
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
從上面代碼中我們可以看到mCropToPadding
如果為true
砍鸠,canvas
會(huì)劃出除去內(nèi)邊距的實(shí)際顯示區(qū)域,這里我們還發(fā)現(xiàn)除了考慮到padding
屬性耕驰,還要考慮mScrollX
mScrollY
兩個(gè)屬性爷辱,為什么呢?
The offset, in pixels, by which the content of this view is scrolled
官方的解釋是內(nèi)容區(qū)域在View中滾動(dòng)偏移量朦肘,View容器實(shí)際是不會(huì)滾動(dòng)的饭弓,真正滾動(dòng)效果是通過內(nèi)容的移動(dòng)達(dá)到滾動(dòng)(偏移量,也可以理解成位移)媒抠,而padding
是包括在內(nèi)容區(qū)域里的弟断,這兩個(gè)偏移量會(huì)讓padding
區(qū)域滾動(dòng),從而導(dǎo)致原有padding
效果失效趴生。以下代碼是View
類中獲取繪制區(qū)域的方法阀趴,繪制區(qū)域是考慮到Scroll
偏移的昏翰。
public void getDrawingRect(Rect outRect) {
outRect.left = mScrollX;
outRect.top = mScrollY;
outRect.right = mScrollX + (mRight - mLeft);
outRect.bottom = mScrollY + (mBottom - mTop);
}
為了讓padding
區(qū)域是基于View
,所以我們要事先繪制好期望的內(nèi)容區(qū)域刘急,避免Scroll
帶來的影響矩父。畫了張圖。
除了Scroll
偏移會(huì)影響邊距排霉,我們能從代碼中發(fā)現(xiàn)Matrix
矩陣縮放也會(huì)影響到邊距窍株,大家可以回到上面講ScaleType
的部分,我們可以看到CENTER , CENTER_CROP , CENTER_INSIDE , FIT_CENTER , FIT_START , FIT_END
的矩陣變換都是基于不包括內(nèi)邊距的區(qū)域的(·vheight
攻柠,vwidth
)球订,而MATRIX
只是設(shè)置了下mDrawMatix
,沒有前幾種縮放類型做的范圍處理瑰钮。onDraw
代碼中我們也能看到是先translate
確定繪制起點(diǎn)冒滩,然后再contact
矩陣變換,如果我們這樣做浪谴。
<ImageView
android:id="@+id/imageView"
android:layout_width="80dp"
android:layout_height="80dp"
android:background="@color/colorAccent"
android:cropToPadding="false"
android:padding="10dp"
android:scaleType="matrix"
android:src="@mipmap/ic_launcher" />
ImageView imageView = (ImageView) findViewById(R.id.imageView);
Matrix matrix = new Matrix();
matrix.setTranslate(-40, -40);
imageView.setImageMatrix(matrix);
最后我們看到的效果是這樣的开睡,同樣我們理解中的padding
失效了
如果我們把
cropToPadding
屬性設(shè)置為true
則得到下面的效果。所以總結(jié)來說
cropToPadding
是ImageView
為了補(bǔ)償其他屬性導(dǎo)致我們最終的“理解的內(nèi)邊距效果”實(shí)現(xiàn)而做出的調(diào)整苟耻,目前了解到如果你的ImageView
設(shè)置了scrollX
scrollY
或者將scaleType
為MATRIX
時(shí)又需要保證padding
的效果篇恒,可以將cropToPadding
設(shè)置為true
。
adjustViewBounds屬性
我們可以通過ScaleType
讓顯示的圖片根據(jù)ImageView
大小進(jìn)行各種類型的調(diào)整凶杖,我們可不可以讓ImageView
根據(jù)圖片內(nèi)容調(diào)整大小呢胁艰?這時(shí)候我們就需要用到adjustViewBounds
這個(gè)屬性啦,能讓顯示的圖片原有比例智蝠,調(diào)整ImageView
到合適的大小腾么,達(dá)到我們想要的圖片與ImageView
無縫銜接,我們從ImageView
代碼中看下是怎么實(shí)現(xiàn)的杈湾。老規(guī)矩搜索mAdjustViewBounds
屬性解虱,就可以發(fā)現(xiàn)這個(gè)屬性主要是在onMeasure
方法中起作用。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
resolveUri();
int w;
int h;
// 期望的顯示比例(不包括padding)漆撞,也就是要顯示圖片的原有比例
float desiredAspect = 0.0f;
// 用于判斷是否允許調(diào)整寬度殴泰,默認(rèn)為false
boolean resizeWidth = false;
// 用于判斷是否允許調(diào)整高度,默認(rèn)為false
boolean resizeHeight = false;
//獲取父容器為ImageView設(shè)置的尺寸測(cè)量模式
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (mDrawable == null) {
mDrawableWidth = -1;
mDrawableHeight = -1;
w = h = 0;
} else {
w = mDrawableWidth;
h = mDrawableHeight;
if (w <= 0) w = 1;
if (h <= 0) h = 1;
//如果設(shè)置mAdjustViewBounds為true叫挟,判斷widthSpecMode和heightSpecMode艰匙,如果不是EXACTLY則支持調(diào)整,并獲得期望的寬高比
if (mAdjustViewBounds) {
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
desiredAspect = (float) w / (float) h;
}
}
int pleft = mPaddingLeft;
int pright = mPaddingRight;
int ptop = mPaddingTop;
int pbottom = mPaddingBottom;
int widthSize;
int heightSize;
//如果寬或者高可以調(diào)整則進(jìn)行調(diào)整抹恳,并且只能調(diào)整一個(gè)
if (resizeWidth || resizeHeight) {
// 獲取最大可能的寬度
widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
// 獲取最大可能的高度
heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
if (desiredAspect != 0.0f) {
// 查看實(shí)際的寬高比
float actualAspect = (float)(widthSize - pleft - pright) /
(heightSize - ptop - pbottom);
// 如果實(shí)際比例與期望比例差距小于0.0000001則不進(jìn)行調(diào)整
if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
boolean done = false;
// 調(diào)整寬度去適應(yīng)高
if (resizeWidth) {
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;
//是否允許調(diào)整寬度员凝,即高度固定,另一個(gè)參數(shù)是和兼容性有關(guān)先不研究
if (!resizeHeight && !mAdjustViewBoundsCompat) {
//計(jì)算調(diào)整后的高度
widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
}
//調(diào)整高度符合規(guī)則奋献,調(diào)整結(jié)束
if (newWidth <= widthSize) {
widthSize = newWidth;
done = true;
}
}
// 調(diào)整高度健霹,思路和寬度一致
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;
if (!resizeWidth && !mAdjustViewBoundsCompat) {
heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
heightMeasureSpec);
}
if (newHeight <= heightSize) {
heightSize = newHeight;
}
}
}
}
} else {
// 如果不需要尺寸調(diào)整旺上,只做常規(guī)的測(cè)量計(jì)算
w += pleft + pright;
h += ptop + pbottom;
w = Math.max(w, getSuggestedMinimumWidth());
h = Math.max(h, getSuggestedMinimumHeight());
// 計(jì)算高度和狀態(tài)
widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
}
setMeasuredDimension(widthSize, heightSize);
}
上面遇到了resolveAdjustedSize
resolveSizeAndState
之前沒遇到過,接下看看下這兩個(gè)方法糖埋。
private int resolveAdjustedSize(int desiredSize, int maxSize,
int measureSpec) {
int result = desiredSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
// 父容器沒有約束宣吱,如果期望值在最大值范圍內(nèi)則取期望值
result = Math.min(desiredSize, maxSize);
break;
case MeasureSpec.AT_MOST:
// 父容器給出調(diào)整的最大值,這個(gè)值與期望值瞳别,設(shè)置的最大值之間取最小值
result = Math.min(Math.min(desiredSize, specSize), maxSize);
break;
case MeasureSpec.EXACTLY:
// 沒有選擇征候,直接返回父容器分配的值
result = specSize;
break;
}
return result;
}
resolveAdjustedSize
就是通過父容器分配的MeasureSpec
的約束下盡可能去取期望值。
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState&MEASURED_STATE_MASK);
}
resolveSizeAndState
是父類View
中的一個(gè)靜態(tài)方法祟敛,和上面resolveAdjustedSize
差不多疤坝,也是根據(jù)父容器分配的MeasureSpec
的約束下盡可能去取要顯示Drawable
的寬高,也包括一些位操作用于表示狀態(tài)馆铁。
了解了原理我們可以結(jié)合實(shí)際情況分析一下跑揉。
(1)寬度設(shè)置為match_parent
高度設(shè)置為wrap_content
,第一張圖為adjustViewBounds
為 false
,第二張圖為true
match_parent
對(duì)應(yīng)的specMode
為EXACTLY
,wrap_content
的specMode
為AT_MOST
,如果將adjustViewBounds
設(shè)置為true
則ImageView
的高度依據(jù)圖片的原有比例調(diào)整埠巨,否則ImageView
的高度為顯示圖片的高度历谍。(2)寬度與高度設(shè)置均為
wrap_content
,無論adjustViewBounds
取值如何辣垒,效果一樣望侈。wrap_content
的specMode
為AT_MOST
,從onMeasure
方法中可知,如果兩個(gè)尺寸都不定的話乍构,是不會(huì)進(jìn)行調(diào)整甜无。(3)寬度與高度設(shè)置均為
match_parent
,無論adjustViewBounds
取值如何哥遮,效果也都一樣。wrap_content
的specMode
為EXACTLY
陵究,所以說寬和高均不可變眠饮,也就是說不能調(diào)整,同理如果寬高設(shè)置為具體尺寸铜邮,adjustViewBounds
也是起不了作用的仪召。
ImageView拓展
ImageView
是我們UI繪制過程一個(gè)必不可少的基礎(chǔ)控件,然而在實(shí)際開發(fā)過程中我們發(fā)現(xiàn)有些情況下ImageView
功能還不夠松蒜,所以我們會(huì)基于ImageView
基礎(chǔ)上去拓展扔茅,這里找了兩個(gè)小案例。
RoundImageView
現(xiàn)在很多應(yīng)用中秸苗,頭像采用是圓形的召娜,而ImageVeiw
只能顯示方形圖片,最簡(jiǎn)單的方法是在加一層背景色遮罩惊楼,當(dāng)然也可以通過自定View來解決玖瘸。這是一個(gè)自定義控件用來顯示圓形圖片秸讹,當(dāng)然現(xiàn)在也有很多圖片庫來顯示圓形頭像。具體代碼就不貼了雅倒,實(shí)現(xiàn)思路主要是繼承ImageView
璃诀,重寫OnDraw
,通過設(shè)置Xfermodes
(這里我理解為圖形疊加模式)在圓形上繪制圖片蔑匣,達(dá)到繪制圓形圖像的效果劣欢。
public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) {
Bitmap scaledSrcBmp;
int diameter = radius * 2;
...
// 為了防止寬高不相等,造成圓形圖片變形裁良,因此截取長方形中處于中間位置最大的正方形圖片
int bmpWidth = bmp.getWidth();
int bmpHeight = bmp.getHeight();
int squareWidth = 0, squareHeight = 0;
int x = 0, y = 0;
Bitmap squareBitmap;
if (bmpHeight > bmpWidth) {
squareWidth = squareHeight = bmpWidth;
x = 0;
y = (bmpHeight - bmpWidth) / 2;
squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight);
} else if (bmpHeight < bmpWidth) {
squareWidth = squareHeight = bmpHeight;
x = (bmpWidth - bmpHeight) / 2;
y = 0;
squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight);
} else {
squareBitmap = bmp;
}
//將正方形圖片縮放至與ImageView直徑相同
if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) {
scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true);
} else {
scaledSrcBmp = squareBitmap;
}
Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(),
scaledSrcBmp.getHeight(),
Bitmap.Config.ARGB_8888);
//獲取處理圖片的Canvas
Canvas canvas = new
Canvas(output);
Paint paint = new Paint();
Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight());
paint.setAntiAlias(true);//開啟抗鋸齒
paint.setFilterBitmap(true);//開啟過濾效果
paint.setDither(true);//開啟抖動(dòng)
canvas.drawARGB(0, 0, 0, 0);
//繪制圓形
canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint);
//設(shè)置疊加模式為SRC_IN,即在圓形的返回內(nèi)繪制圖像
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(scaledSrcBmp, rect, rect, paint);
//釋放內(nèi)存
bmp = null;
squareBitmap = null;
scaledSrcBmp = null;
return output;
}
onDraw
繪制圓形圖片
@Override
protected void onDraw(Canvas canvas) {
...
Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius);
canvas.drawBitmap(roundBitmap, defaultWidth / 2
- radius, defaultHeight / 2
- radius, null);
}
AutoSquareImageView
這是我做Android項(xiàng)目遇到的第一個(gè)難題凿将,四個(gè)ImageView
是要通過weight
屬性來實(shí)現(xiàn)水平方向上的寬度均等,但是UI要求是讓這個(gè)圖形按鈕正方形顯示趴久,不同手機(jī)水平寬度不同丸相,需要自適應(yīng)高度,為了達(dá)到正方形效果需要自定義AutoSquareImageView
彼棍,修改思路其實(shí)很簡(jiǎn)單灭忠,將ImageView
寬度通過weight
去獲取,高度設(shè)置為wrap_content
座硕,重寫onMeasure
方法弛作,計(jì)算正常流程下的寬與高,然后取大值達(dá)到滿足正方形圖的最小邊長华匾。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
//保持長度大的一邊
if (measuredWidth > measuredHeight) {
setMeasuredDimension(measuredWidth, measuredWidth);
} else {
setMeasuredDimension(measuredHeight, measuredHeight);
}
}
總結(jié)
-
ImageView
顯示的圖片資源可以是通過XML屬性或者Java代碼指定顯示的圖片內(nèi)容映琳,可以通過設(shè)置Resource
、Uri
蜘拉、Bitmap
對(duì)象萨西、Drawable
對(duì)象來指定。 -
ScaleType
屬性的修改本質(zhì)上根據(jù)設(shè)定的枚舉將變換賦值給mMatrix
旭旭,最終通過onDraw
中的矩陣操作實(shí)現(xiàn)谎脯。 -
cropToPadding
屬性可以讓圖片內(nèi)容是在padding
基礎(chǔ)上進(jìn)行變換(縮放,移動(dòng))持寄,根據(jù)實(shí)踐如果使用了mScrollX
mScrollY
屬性或者scaleType
設(shè)置為MATRIX
類型時(shí)會(huì)配合用到這個(gè)屬性源梭。 -
adjustViewBounds
屬性主要是讓ImageView
根據(jù)圖片原有比例的約束下將調(diào)整大小,注意必須要限定住寬或者高稍味,即將其中一個(gè)屬性設(shè)置為match_parent
或者確定尺寸废麻。
筆記就寫到這里了,這也是我第一次在網(wǎng)上寫文章模庐,寫的也是基礎(chǔ)知識(shí)烛愧,如果有什么不對(duì)的地方歡迎大家批評(píng)指正!寫文章也是為了能把學(xué)習(xí)的知識(shí)點(diǎn)進(jìn)行梳理,然后也會(huì)有學(xué)習(xí)的動(dòng)力屑彻,內(nèi)容可能不高級(jí)验庙,但希望能一點(diǎn)點(diǎn)慢慢提高。