編輯:關於Android編程
大家知道,自定義View有三個重要的步驟:measure,layout,draw。而measure處於該鏈條的首端,占據著極其重要的地位;然而對於measure的理解卻不是那麼容易,許多問題都是一知半解,比如:為什麼父View影響到了子View的MeasureSpec的生成?為什麼我們自定義一個View在布局時將其寬或者高指定為wrap_content但是其實際是match_parent的效果?子View的specMode和specSize的生成依據又是什麼?這些問題以前一直困擾著我,我就去找資料看,搜了一大筐,沮喪的發現這些文章大同小異:只舉個簡單的例子,很少研究為什麼;人雲亦雲,文章裡的內容沒有去驗證和深究就發出來了;或者避重就輕直接把難點給繞過去了…….每次,看完這些文章就沒有勇氣去看layout和draw了,就算了;這可能就是《自定義View——從入門到放棄》的劇本吧。看了那麼多文章依舊不能解答原來的疑惑;就像聽過了許多大道理依舊不過好這一生。連measure都沒有很好的理解又何談真正的理解layout和draw呢?要是能找到一篇文章能解開這些疑惑該有多好呀!
咦,等等。要是一直沒有找到這麼一篇文章那又怎麼辦呢?就真的不學習了?
MeasureSpec基礎知識
系統顯示一個View,首先需要通過測量(measure)該View來知曉其長和寬從而確定顯示該View時需要多大的空間。在測量的過程中MeasureSpec貫穿全程,發揮著不可或缺的作用。
所以,了解View的測量過程,最合適的切入點就是MeasureSpec。
我們先來瞅瞅官方文檔對於MeasureSpec 的介紹:
A MeasureSpec encapsulates the layout requirements passed from parent to child.Each MeasureSpec represents a requirement for either the width or the height.A MeasureSpec is comprised of a size and a mode.
請注意這段話所包含的重要信息點:
1 MeasureSpec封裝了父布局傳遞給子View的布局要求。
2 MeasureSpec可以表示寬和高
3 MeasureSpec由size和mode組成
MeasureSpec通常翻譯為”測量規格”,它是一個32位的int數據.
其中高2位代表SpecMode即某種測量模式,低30位為SpecSize代表在該模式下的規格大小.
可以通過如下方式分別獲取這兩個值:
int specSize = MeasureSpec.getSize(measureSpec)
獲取SpecSize
int specMode = MeasureSpec.getMode(measureSpec)
獲取specMode
當然,也可以通過這兩個值生成新的MeasureSpec
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
SpecMode一共有三種:
MeasureSpec.EXACTLY , MeasureSpec.AT_MOST , MeasureSpec.UNSPECIFIED
嗯哼,它們已經躺在這裡了,我們來挨個瞅瞅,每個SpecMode是什麼意思
MeasureSpec.EXACTLY
官方文檔的描述:
The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.
MeasureSpec.EXACTLY模式表示:父容器已經檢測出子View所需要的精確大小。
在該模式下,View的測量大小即為SpecSize。
MeasureSpec.AT_MOST
官方文檔的描述:
The child can be as large as it wants up to the specified size.
MeasureSpec.AT_MOST模式表示:父容器未能檢測出子View所需要的精確大小,但是指定了一個可用大小即specSize
在該模式下,View的測量大小不能超過SpecSize。
MeasureSpec.UNSPECIFIED
官方文檔的描述:
The parent has not imposed any constraint on the child. It can be whatever size it wants.
父容器不對子View的大小做限制.
MeasureSpec.UNSPECIFIED這種模式一般用作Android系統內部,或者ListView和ScrollView等滑動控件,在此不做討論。
看完了這三個SpecMode的含義,我們再從源碼裡看看它們是怎麼形成的。
在ViewGroup中測量子View時會調用到measureChildWithMargins()方法,或者與之類似的方法。源碼如下:
/**
* @param child
* 子View
* @param parentWidthMeasureSpec
* 父容器(比如LinearLayout)的寬的MeasureSpec
* @param widthUsed
* 父容器(比如LinearLayout)在水平方向已經占用的空間大小
* @param parentHeightMeasureSpec
* 父容器(比如LinearLayout)的高的MeasureSpec
* @param heightUsed
* 父容器(比如LinearLayout)在垂直方向已經占用的空間大小
*/
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec =
getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
final int childHeightMeasureSpec =
getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom +
lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
請務必注意該方法的參數;明白這幾個參數的含義才能准確理解方法的實現。
通過這些參數看出來一些端倪,該方法要測量子View傳進來的參數卻包含了父容器的寬的MeasureSpec,父容器在水平方向已經占用的空間大小,父容器的高的MeasureSpec,父容器在垂直方向已經占用的空間大小等父View相關的信息。這在一定程度體現了:父View影響著子View的MeasureSpec的生成。
該方法主要有四步操作:
第一步:
得到子View的LayoutParams,請參見第15行代碼。
第二步:
得到子View的寬的MeasureSpec,請參見第16-18行代碼。
第三步:
得到子View的高的MeasureSpec,請參見第19-21行代碼。
第四步:
測量子View,請參見第22行代碼。
第一步,沒啥好說的;第二步和第三步都調用到了getChildMeasureSpec( ),在該方法內部又做了哪些操作呢?
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = View.MeasureSpec.getMode(spec);
int specSize = View.MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case View.MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = View.MeasureSpec.AT_MOST;
}
break;
case View.MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = View.MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = View.MeasureSpec.AT_MOST;
}
break;
case View.MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = View.MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = View.MeasureSpec.UNSPECIFIED;
}
break;
}
return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
該方法就是確定子View的MeasureSpec的具體實現。
請注意該方法的參數:
spec
父容器(比如LinearLayout)的寬或高的MeasureSpec
padding
父容器(比如LinearLayout)在垂直方向或者水平方向已被占用的空間。
為什麼這麼說,它的依據在哪裡?
請看在measureChildWithMargins()方法裡調用getChildMeasureSpec()的地方,傳遞給getChildMeasureSpec()的第二個參數是如下構成:
比如:mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
其中:
mPaddingLeft和mPaddingRight表示父容器左右兩內側的padding
lp.leftMargin和lp.rightMargin表示子View左右兩外側的margin
這四部分都不可以再利用起來布局子View.所以說這些值的和表示:父容器在水平方向已經被占用的空間
同理:
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
表示:
父容器(比如LinearLayout)在垂直方向已被占用的空間.
childDimension
通過子View的LayoutParams獲取到的子View的寬或高
所以,從getChildMeasureSpec()方法的第一個參數spec和第二個參數padding也可以看出:
父容器(如LinearLayout)的MeasureSpec和子View的LayoutParams共同決定了子View的MeasureSpec!
明白了該方法的參數,我們再來看方法的具體實現步驟。
第一步:
得到父容器的specMode和specSize,請參見第2-3行代碼。
第二步:
得到父容器在水平方向或垂直方向可用的最大空間值,請參見第5行代碼。
第三步:
確定子View的specMode和specSize,請參見第10-50行代碼。
在這裡出現了一個很關鍵的switch語句,該語句的判斷條件就是父View的specMode;在此根據父View的specMode的不同來決定子View的specMode和specSize.
情況1:
父容器的specMode為MeasureSpec.EXACTLY,請參見第11-22行代碼。
也請記住該先決條件,因為以下的討論都是基於此展開的。
我們首先看到一個if判斷if (childDimension >= 0),或許看到這有點懵了:childDimension>=0是啥意思?難道還有小於0的情況?是的,請注意兩個系統常量:
LayoutParams.MATCH_PARENT=-1和LayoutParams.WRAP_CONTENT=-2
所以在此處的代碼:
if (childDimension >= 0)
表示子View的寬或高不是match_parent,也不是wrap_content而是一個具體的數值,比如100px。
那麼:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
看完這個if,我們來看第一個else if
else if (childDimension == LayoutParams.MATCH_PARENT)
表示子View的寬或高是LayoutParams.MATCH_PARENT。
那麼:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode也為MeasureSpec.EXACTLY,即:
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
我們來看第二個else if
else if (childDimension == LayoutParams.WRAP_CONTENT)
表示子View的寬或高是LayoutParams.WRAP_CONTENT。
那麼:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode為MeasureSpec.AT_MOST,即:
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
情況2:
父容器的specMode為MeasureSpec.AT_MOST,請參見第24-35行代碼。
也請記住該先決條件,因為以下的討論都是基於此展開的。
還是先看這個if判斷
if (childDimension >= 0)
表示子View的寬或高不是match_parent,也不是wrap_content而是一個具體的數值,比如100px。
那麼:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
繼續看第一個else if
else if (childDimension == LayoutParams.MATCH_PARENT)
表示子View的寬或高是LayoutParams.MATCH_PARENT。
那麼:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode也為MeasureSpec.AT_MOST,即:
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
接著看第二個else if
else if (childDimension == LayoutParams.WRAP_CONTENT)
表示子View的寬或高是LayoutParams.WRAP_CONTENT。
那麼:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode也為MeasureSpec.AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
情況3:
父容器的specMode為MeasureSpec.UNSPECIFIED,請參見第37-48行代碼。
也請記住該先決條件,因為以下的討論都是基於此展開的。
還是先看這個if判斷
if (childDimension >= 0)
表示子View的寬或高不是match_parent,也不是wrap_content而是一個具體的數值,比如100px。
那麼:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
繼續看第一個else if
else if (childDimension == LayoutParams.MATCH_PARENT)
表示子View的寬或高是LayoutParams.MATCH_PARENT。
那麼:子View的size為0,子View的mode為MeasureSpec.UNSPECIFIED,即:
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
接著看第二個else if
else if (childDimension == LayoutParams.WRAP_CONTENT)
表示子View的寬或高是LayoutParams.WRAP_CONTENT。
那麼:子View的size為0,子View的mode為MeasureSpec.UNSPECIFIED,即:
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
至此,我們可以清楚地看到:
子View的MeasureSpec由其父容器的MeasureSpec和該子View本身的布局參數LayoutParams共同決定。
在此經過測量得出的子View的MeasureSpec是系統給出的一個期望值(參考值),我們也可摒棄系統的這個測量流程,直接調用setMeasuredDimension( )設置子View的寬和高的測量值。
對於以上的分析可用表格來規整各一下MeasureSpec的生成
好了,看到這個圖,感覺清晰多了。為了便於理解和記憶,我在此再用大白話再對該圖進行詳細的描述:
在哪些具體的情況下子View的SpecMode為MeasureSpec.EXACTLY?
在此,對各情況一一討論和分析:
第一種情況:
當子View的LayoutParams的寬(高)采用具體的值(如100px)時且父容器的MeasureSpec為MeasureSpec.EXACTLY或者MeasureSpec.AT_MOST或者MeasureSpec.UNSPECIFIED時:系統返回給該子View的specMode就為MeasureSpec.EXACTLY,系統返回給該子View的specSize就為子View自己指定的大小(childSize)。
通俗地理解:
子View的LayoutParams的寬(高)采用具體的值(如100px)時,那麼說明該子View的大小是非常明確的,明確到了令人發指的地址,都已經到了用具體px值指定的地步了。那麼此時不管父容器的specMode是什麼,系統返回給該子View的specMode總是MeasureSpec.EXACTLY,並且系統返回給該子View的specSize就是子View自己指定的大小(childSize)。
第二種情況:
當子View的LayoutParams的寬(高)采用match_parent時並且父容器的MeasureSpec為MeasureSpec.EXACTLY時:系統返回給該子View的specMode就為 MeasureSpec.EXACTLY,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)。
通俗地理解:
子View的LayoutParams的寬(高)采用match_parent並且父容器的MeasureSpec為MeasureSpec.EXACTLY。這時候說明子View的大小還是挺明確的:就是要和父容器一樣大,更加直白地說就是父容器要怎樣子View就要怎樣。所以,如果父容器MeasureSpec為MeasureSpec.EXACTLY,那麼系統返回給該子View的specMode就為 MeasureSpec.EXACTLY和父容器一樣;系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize),即為父容器的剩余大小.
在哪些具體的情況下子View的SpecMode為MeasureSpec.AT_MOST?
在此,對各情況一一討論和分析:
第一種情況:
當子View的LayoutParams的寬(高)采用match_parent並且父容器的MeasureSpec為MeasureSpec.AT_MOST時:系統返回給該子View的specMode就為MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)
通俗地理解:
子View的LayoutParams的寬(高)采用match_parent並且父容器的MeasureSpec為MeasureSpec.AT_MOST。這時候說明子View的大小還是挺明確的:就是要和父容器一樣大,直白地說就是父容器要怎樣子View就要怎樣。但是此時父容器的大小不是很明確其MeasureSpec為MeasureSpec.AT_MOST,那麼系統返回給該子View的specMode就為MeasureSpec.AT_MOST和父容器一樣;系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize),即為父容器的剩余大小.
第二種情況:
當子View的LayoutParams的寬(高)采用wrap_content時並且父容器的MeasureSpec為MeasureSpec.EXACTLY時:系統返回給該子View的specMode就為 MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)
通俗地理解:
子View的LayoutParams的寬(高)采用wrap_content時說明這個子View的寬高不明確,要視content而定。這時如果父容器的MeasureSpec為MeasureSpec.EXACTLY即父容器是一個精確模式。這種情況概況起來簡單地說就是:子View大小是不確定的,但父容器大小是確定的,那麼系統返回給該子View的specMode也就是不確定的即為MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)
第三種情況:
當子View的LayoutParams的寬(高)采用wrap_content時並且父容器的MeasureSpec為MeasureSpec.AT_MOST時:系統返回給該子View的specMode就為MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)
通俗地理解:
子View的LayoutParams的寬(高)采用wrap_content,即說明這個子View的寬高不明確,要視content而定。這個時候父容器的MeasureSpec為MeasureSpec.AT_MOST。這種情況概況起來簡單地說就是:子View的寬高是不確定的,父容器的寬高也是不確定的,那麼系統返回給該子View的specMode也就是不確定的即為MeasureSpec.AT_MOST,系統返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)
在哪些具體的情況下子View的SpecMode為MeasureSpec.UNSPECIFIED?
前面也說了該模式在實際開發中極少用到,故在此不做討論。
好了,搞懂了MeasureSpec我們才正真地進入到onMeasure()源碼分析。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure( )源碼流程如下:
(1) 在onMeasure調用setMeasuredDimension( )設置View的寬和高.
(2) 在setMeasuredDimension()中調用getDefaultSize()獲取View的寬和高.
(3) 在getDefaultSize()方法中又會調用到getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()獲取到View寬和高的最小值.
即這一系列的方法調用順序為:
為了理清這幾個方法間的調用及其作用,在此按照倒序分析每個方法的源碼。
先來看getSuggestedMinimumWidth( )
//Returns the suggested minimum width that the view should use
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
該方法返回View的寬度的最小值MinimumWidth.
在此需要注意該View是否有背景.
(1) 若該View沒有背景。
那麼該MinimumWidth為View本身的最小寬度即mMinWidth。
有兩種方法可以設置該mMinWidth值:
第一種:XML布局文件中定義minWidth
第二種:調用View的setMinimumWidth()方法為該值賦值
(2) 若該View有背景。
那麼該MinimumWidth為View本身最小寬度mMinWidth和View背景的最小寬度的最大值
getSuggestedMinimumHeight()方法與此處分析很類似,故不再贅述.
接下來看看getDefaultSize( )的源碼
public static int getDefaultSize(int size, int measureSpec) {
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:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
該方法用於獲取View的寬或者高的大小。
該方法的第一個輸入參數size就是調用getSuggestedMinimumWidth()方法獲得的View的寬或高的最小值。
從getDefaultSize()的源碼裡的switch可看出該方法的返回值有兩種情況:
(1) measureSpec的specMode為MeasureSpec.UNSPECIFIED
在此情況下該方法的返回值就是View的寬或者高最小值.
該情況很少見,基本上可忽略
(2) measureSpec的specMode為MeasureSpec.AT_MOST或MeasureSpec.EXACTLY:
在此情況下getDefaultSize()的返回值就是該子View的measureSpec中的specSize。
除去第一種情況不考慮以外,可知:
在measure階段View的寬和高由其measureSpec中的specSize決定!!
看了這麼久的源碼,我們終於搞清楚了這個問題;但是剛剛舒展開的眉頭又皺起來了。結合剛才的圖發現一個問題:在該圖的最後一行,如果子View在XML布局文件中對於大小的設置采用wrap_content,那麼不管父View的specMode是MeasureSpec.AT_MOST還是MeasureSpec.EXACTLY對於子View而言系統給它設置的specMode都是MeasureSpec.AT_MOST,並且其大小都是parentLeftSize即父View目前剩余的可用空間。這時wrap_content就失去了原本的意義,變成了match_parent一樣了.
所以自定義View在重寫onMeasure()的過程中應該手動處理View的寬或高為wrap_content的情況。
至此,已經看完了getSuggestedMinimumWidth()和getDefaultSize()
最後再來看setMeasuredDimension( )的源碼
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= MEASURED_DIMENSION_SET;
}
該方法用於設置View寬和高的測量值。
好了,關於onMeasure( )的源碼及其調用流程都已經分析完了。
但是,剛才還遺留了一個問題:
自定義View在重寫onMeasure()的過程中要處理View的寬或高為wrap_content的情況(請參見下圖中的綠色標記部分)
我們該怎麼處理呢?
第一種情況:
如果在xml布局中View的寬和高均用wrap_content.那麼需要設置
View的寬和高為mWidth和mHeight.
第二種情況:
如果在xml布局中View的寬或高其中一個為wrap_content,那麼就將該值設置為默認的寬或高,另外的一個值采用系統測量的specSize即可.
具體的實現可以這樣做:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec , heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, mHeight);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, heightSpceSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpceSize, mHeight);
}
}
該部分的處理主要有兩步
第一步:
調用super.onMeasure(),請參見第2行代碼
第二步:
處理子View的大小為wrap_content的情況,請參見第3-14行代碼。
此處涉及到的mWidth和mHeight均為一個默認值;應根據具體情況而設值。
其實,Andorid系統的控件比如TextView等也在onMeasure()中對其大小為wrap_content這一情況作了特殊的處理。
請注意在第二步的代碼中用的判斷條件:
widthSpecMode==MeasureSpec.AT_MOST或者heightSpecMode==MeasureSpec.AT_MOST或者兼而有之;總之,這裡的著眼點是模式MeasureSpec.AT_MOST。
看到這裡再聯想到下圖就有一個疑問了(請參見下圖中的紅色標記部分):
如果子View在布局文件中采用match_parent,並且父容器的SpecMode為MeasureSpec.AT_MOST, 那麼此時該子View的SpecMode也為MeasureSpec.AT_MOST且其View的大小為parentLeftSize。
既然此時該子View的SpecMode也為MeasureSpec.AT_MOST那麼當執行到onMeasure()時按照我們的判斷邏輯,它的寬或者高至少有一個會被設置成默認值(mWidth和mHeight)。說白了,本來是個match_parent,卻被設置成了具體指的默認值。
看到這裡好像覺得也沒啥不對,但是不符合常理!!!到底是哪裡錯了??? 我們這麼想:
在什麼情況下父容器的SpecMode為MeasureSpec.AT_MOST?
這個問題不難回答,共有兩種情況:
情況1:
當父容器的大小為wrap_content時系統給父容器的SpecMode為MeasureSpec.AT_MOST.
情況2:
當父容器的大小為match_parent時系統給父容器的SpecMode為MeasureSpec.AT_MOST.
回答了這個問題就以此答案為基礎繼續討論。
剛才的問題就可以描述為以下兩種情況:
情況1:
當父容器大小為wrap_content且其specMode為MeasureSpec.AT_MOST,子View大小為match_parent。
也就是說:子View想和父容器一樣大但父容器的大小又設定為包裹內容大小即wrap_content。那麼,到底誰決定誰呢?誰也不能決定誰!父View和子View這父子倆就這麼耗上了。
所以,該情況是理論上存在的但在實際情況中是很不合理甚至錯誤的,當然也是不可取的。
情況2:
當父容器大小為match_parent且其SpecMode為MeasureSpec.AT_MOST,子View大小為match_parent。
既然父容器大小為match_parent且其SpecMode為MeasureSpec.AT_MOST,那麼父容器的父容器(以下簡稱“爺容器”)又該是什麼情況呢?
1 爺容器的大小不可能是wrap_content(原理同情況1)
2 爺容器的大小不可能是某個具體的值。
因為若其大小為某具體值,那麼其specMode應該為MeasureSpec.EXACTLY;父容器的specMode也該為MeasureSpec.EXACTLY。但是這裡父容器的SpecMode為MeasureSpec.AT_MOST,相互矛盾了。
3 爺容器的大小是match_parent;那麼其SpecMode有兩種情況:MeasureSpec.EXACTLY或者MeasureSpec.AT_MOST。
在此,為了便於理清思路,繼續分情況來討論
第一種情況:
爺容器的大小是match_parent,SpecMode為MeasureSpec.EXACTLY,且父容器此時大小為match_parent;那麼父容器的SpecMode應該為MeasureSpec.EXACTLY;但是這裡父容器的SpecMode為MeasureSpec.AT_MOST。兩者是矛盾的。所以不會出現這個情況。
第二種情況:
爺容器的大小是match_parent,SpecMode為MeasureSpec.AT_MOST,且父容器此時大小為match_parent,那麼父容器的SpecMode可以為MeasureSpec.AT_MOST。這是唯一可能存在的情況。
試著將這種情況抽取出來,就陷入到一個循環:子View大小是match_parent其SpecMode為MeasureSpec.AT_MOST;父View的大小也是match_parent其SpecMode也為MeasureSpec.AT_MOST,爺容器亦如此……..直到根View根為止。但是根View的大小如果為match_parent,那麼其SpecMode必為MeasureSpec.EXACTLY。所以這種情況也是矛盾的,也不會出現。
至此,綜上所述,我們發現:子View在布局文件中采用match_parent,並且父容器的SpecMode為MeasureSpec.AT_MOST,那麼此時該子View的SpecMode也為MeasureSpec.AT_MOST且其View的大小為parentLeftSize這種情況本身(圖中紅色標記部分)就是不合理的,不可取的。將這個問題再次抽取就可以簡化為情況1,殊途同歸。
以此類推:
(1) 不可能出現根View的大小為wrap_content但它的一個子View大小為match_parent。
(2) 從根到這個子View的父容器都是wrap_content,而子View的大小為match_parent。這個極端情況也是不會的,可見情況1的分析.
(3)從根到這個子View的父容器都是wrap_content,而子View大小也為wrap_content。這是個正常情況也正是我們改良後的onMeasure()來專門處理的子View大小為wrap_content的情況。
嗯哼,終於看完了measure的過程。
當理解了MeasureSpec和measure的原理,我們才能更好的理解layout和draw從而掌握自定義View的流程。
who is the next one? ——> layout.
首先我將貼出幾種實現圓角邊框的dmeo程序效果圖:方式一:使用shape元素填充背景,設置圓角/帶弧度的角1、首先在 \res\drawable下新建Shape為根元素的
AlertDialog生成的對話框可分為4個區域:圖標區,標題區,內容區,按鈕區結構如圖:AlertDialog對話框的使用:1,創建AlertDialog.Builde
二維碼:是用某種特定的幾何圖形按一定規律在平面(二維方向上)分布的黑白相間的圖形記錄數據符號信息的;在代碼編制上巧妙的利用構成計算機內部邏輯基礎的0和1比特流的概念,使用
開始之前 最近學習了一下NDK的開發, 就來分享一下. 對一個新鮮事物, 我們先解決的無非就是三件事情: 是什麼?為什麼?怎麼做?.NDK簡介 (英語:native de