解读ImageView的wrap_content和adjustViewBounds的工作原理
目錄
前言
1、wrap_content
2、adjustViewBounds
3、其他組件
前言
ImageView是android開發(fā)過程中經(jīng)常會使用的一種組件,由于android屏幕碎片化的問題,有時候我們無法設(shè)定一個具體的寬高。比如說width是match_parent的,這時候我們還想讓圖片在寬度完全填充并能正常顯示,我們直接會想到將height設(shè)置為wrap_content。但是用過的同學(xué)都知道ImageView的實際區(qū)域要大于圖片區(qū)域,如圖:
?
可以看到在圖片的上下留了很大的空白空間,有人可能想設(shè)置fitXY來解決這個問題。結(jié)果是ImageView區(qū)域未變,但是圖片變形了,如圖:
上面這種情況出現(xiàn)在圖片實際尺寸要大于屏幕尺寸(或為ImageView設(shè)定的尺寸)。那如果圖片比較小,情況會改善么?
我們同樣觀察設(shè)置fitXY前后的情況,圖片如下:
???
?
可以看到當(dāng)圖片比較小的時候,會左右留出空白,而設(shè)置fitXY后則ImageView區(qū)域依然未改變,所以圖片變形了。
1、wrap_content
那么設(shè)置了wrap_content的ImageView的區(qū)域為什么無法貼合圖片內(nèi)容的大小呢?我們知道View的onMeasure函數(shù)是計算一個view的大小的,那么讓我們來看看ImageView的onMeasure函數(shù),代碼如下:
? @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {resolveUri();int w;int h;// Desired aspect ratio of the view's contents (not including padding)float desiredAspect = 0.0f;// We are allowed to change the view's widthboolean resizeWidth = false;// We are allowed to change the view's heightboolean resizeHeight = false;final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);if (mDrawable == null) {// If no drawable, its intrinsic size is 0.mDrawableWidth = -1;mDrawableHeight = -1;w = h = 0;} else {w = mDrawableWidth;h = mDrawableHeight;if (w <= 0) w = 1;if (h <= 0) h = 1;// We are supposed to adjust view bounds to match the aspect// ratio of our drawable. See if that is possible.if (mAdjustViewBounds) {resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;desiredAspect = (float) w / (float) h;}}final int pleft = mPaddingLeft;final int pright = mPaddingRight;final int ptop = mPaddingTop;final int pbottom = mPaddingBottom;int widthSize;int heightSize;if (resizeWidth || resizeHeight) {// Get the max possible width given our constraintswidthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);// Get the max possible height given our constraintsheightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);if (desiredAspect != 0.0f) {// See what our actual aspect ratio isfinal float actualAspect = (float)(widthSize - pleft - pright) /(heightSize - ptop - pbottom);if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {boolean done = false;// Try adjusting width to be proportional to heightif (resizeWidth) {int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +pleft + pright;// Allow the width to outgrow its original estimate if height is fixed.if (!resizeHeight && !sCompatAdjustViewBounds) {widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);}if (newWidth <= widthSize) {widthSize = newWidth;done = true;}}// Try adjusting height to be proportional to widthif (!done && resizeHeight) {int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +ptop + pbottom;// Allow the height to outgrow its original estimate if width is fixed.if (!resizeWidth && !sCompatAdjustViewBounds) {heightSize = resolveAdjustedSize(newHeight, mMaxHeight,heightMeasureSpec);}if (newHeight <= heightSize) {heightSize = newHeight;}}}}} else {...widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);}setMeasuredDimension(widthSize, heightSize); }
我們一步步來看,首先看這個函數(shù)一開始調(diào)用了resolveUri這個函數(shù),這個函數(shù)代碼如下:
? private void resolveUri() {...if (mResource != 0) {try {d = mContext.getDrawable(mResource);} catch (Exception e) {...}} else if (mUri != null) {d = getDrawableFromUri(mUri);...} else {return;}updateDrawable(d); }private void updateDrawable(Drawable d) {...if (d != null) {...mDrawableWidth = d.getIntrinsicWidth();mDrawableHeight = d.getIntrinsicHeight();...} else {mDrawableWidth = mDrawableHeight = -1;} }
通過上面代碼可以看到resolveUri函數(shù)會先得到drawable對象(從resource或uri中),然后通過updateDrawable將
mDrawableWidth和mDrawableHeight這兩個變量設(shè)置為drawable的寬高。
我們回到onMeasure函數(shù)繼續(xù)往下看,第一個if-else,因為我們討論的是有圖片的情況,所以mDrawable一定不為null,那么走進(jìn)了else語句,
將剛才的mDrawableWidth和mDrawableHeight兩個變量的值賦給了w和h這兩個局部變量。
同時這里如果mAdjustViewBounds為ture,則改變resizeWidth和resizeHeight。而他們的默認(rèn)值是false。這里我們先討論mAdjustViewBounds為false的情況。
再繼續(xù),第二個if-else,判斷是否resize,由于mAdjustViewBounds為false,所以resizeWidth和resizeHeight都為false,走進(jìn)else語句塊。
在else中則用resolveSizeAndState這個函數(shù)來計算寬高,代碼如下:
? public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {final int specMode = MeasureSpec.getMode(measureSpec);final int specSize = MeasureSpec.getSize(measureSpec);final int result;switch (specMode) {case MeasureSpec.AT_MOST:if (specSize < size) {result = specSize | MEASURED_STATE_TOO_SMALL;} else {result = size;}break;case MeasureSpec.EXACTLY:result = specSize;break;case MeasureSpec.UNSPECIFIED:default:result = size;}return result | (childMeasuredState & MEASURED_STATE_MASK); }
首先從measureSpec中獲取mode和size。
?
這里簡單解釋一下measureSpec,它是一個int值,前兩位(32和31位)存儲標(biāo)志位,即specMode;后面則存儲著size即限制大小。而measureSpec是父View傳給子View的,也就是說父View會根據(jù)自身的情況限制子View的大小。
這里還涉及到specMode的三種模式:
①UNSPECIFIED:父View沒有對子View施加任何約束。它可以是任何它想要的大小。?
②EXACTLY:父View已經(jīng)確定了子View的確切尺寸。子View將被限制在給定的界限內(nèi),而忽略其本身的大小。?
③AT_MOST:子View的大小不能超過指定的大小
這部分也值得一說,以后專門開一篇新文章來仔細(xì)講講。
基于上面的解釋,我們回頭在來看resolveAdjustedSize的代碼就比較好理解了。
在從measureSpec中的到了mode和size后,根據(jù)mode不同會有不同的處理。
這里我們有一個隱藏的前提暴露出來了,就是ImageView不能超出它的父view的顯示區(qū)域。即mode只能為EXACTLY或AT_MOST。因為view的寬度是match_parent,所以mode是EXACTLY,直接是父view的寬度,我們就不再考慮了。
那么重點來看高度,因為是wrap_content,所以mode應(yīng)該是AT_MOST,則最終的高度是desiredSize、specSize和maxSize的最小值,desiredSize是前面獲取的圖片的高度,specSize是父view限制的大小。而最終高度則取他們兩個的最小值。
這樣我們就有一個結(jié)論,在我們設(shè)定的前提下,ImageView的寬度是父View的限制寬度,而高度是圖片高度與父View限制高度的較小值。兩者并無關(guān)聯(lián),所以并不會按照圖片的比例計算自己的寬高。所以在這種情況下,wrap_content無法達(dá)到讓ImageView按圖片的比例顯示,這樣就會出現(xiàn)文章開頭的情況。
?
2、adjustViewBounds
上面我們發(fā)現(xiàn)為ImageView設(shè)置了wrap_content無法達(dá)到效果。但是ImageView還有一個adjustViewBounds參數(shù),當(dāng)設(shè)置了這個參數(shù),如下:android:adjustViewBounds="true"
ImageView就可以按照圖片的比例來顯示了。
這是怎么實現(xiàn)的?
接下來我們回過頭看看之前的mAdjustViewBounds,我們上面討論的是它為false的情況。當(dāng)我們設(shè)置了adjustViewBounds,它就為ture了,這時就執(zhí)行if語句中的代碼:
? resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; desiredAspect = (float) w / (float) h; 當(dāng)ImageView的寬高沒有都是設(shè)置為固定值或match_parent時,resizeWidth和resizeHeight一定有一個為ture。
而desiredAspect則是寬高比。
繼續(xù)看onMeasure中接下來的代碼,在第二個if-else時,由于resizeWidth和resizeHeight一定有一個為ture,所以走進(jìn)if語句塊。
首先調(diào)用了resolveAdjustedSize這個函數(shù)來計算寬高。我們先來看看resolveAdjustedSize的代碼:
? private int resolveAdjustedSize(int desiredSize, int maxSize,int measureSpec) {int result = desiredSize;final int specMode = MeasureSpec.getMode(measureSpec);final int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:/* Parent says we can be as big as we want. Just don't be largerthan max size imposed on ourselves.*/result = Math.min(desiredSize, maxSize);break;case MeasureSpec.AT_MOST:// Parent says we can be as big as we want, up to specSize.// Don't be larger than specSize, and don't be larger than// the max size imposed on ourselves.result = Math.min(Math.min(desiredSize, specSize), maxSize);break;case MeasureSpec.EXACTLY:// No choice. Do what we are told.result = specSize;break;}return result; }
與上面resolveSizeAndState方法很類似,當(dāng)mode為AT_MOST時,
result = Math.min(Math.min(desiredSize, specSize), maxSize);
是取圖片size、specSize和maxSize的最小值。
到目前為止沒什么不同,下面才是重點,繼續(xù)看onMeasure下面的代碼:
? final float actualAspect = (float)(widthSize - pleft - pright) /(heightSize - ptop - pbottom); if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {boolean done = false;// Try adjusting width to be proportional to heightif (resizeWidth) {int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +pleft + pright;// Allow the width to outgrow its original estimate if height is fixed.if (!resizeHeight && !sCompatAdjustViewBounds) {widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);}if (newWidth <= widthSize) {widthSize = newWidth;done = true;}}// Try adjusting height to be proportional to widthif (!done && resizeHeight) {int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +ptop + pbottom;// Allow the height to outgrow its original estimate if width is fixed.if (!resizeWidth && !sCompatAdjustViewBounds) {heightSize = resolveAdjustedSize(newHeight, mMaxHeight,heightMeasureSpec);}if (newHeight <= heightSize) {heightSize = newHeight;}} }
當(dāng)計算后的寬高比與圖片寬高比不同時,會根據(jù)之前resizeWidth和resizeHeight,用固定的那個值和圖片寬高比取計算另外一個值。
這樣ImageView的寬高比例就完全符合了圖片的實際寬高比,不會出現(xiàn)文章前面的留白的情況了。
?
3、其他組件
本文討論的是ImageView保持固定的寬高比,那么其他組件也可以么?
有幾種方法可實現(xiàn)的組件的固定寬高比,具體請閱讀《Android中如何使控件保持固定寬高比》。
總結(jié)
以上是生活随笔為你收集整理的解读ImageView的wrap_content和adjustViewBounds的工作原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 非UI线程下页面处理:view的post
- 下一篇: Android中如何使控件保持固定宽高比