Android View從設(shè)計(jì)到顯示到屏幕上,共用了三大步:measure、layout、draw。今天主要講講View是如何測(cè)量的。
以FrameLayout為例,從measure方法開始,如圖1,measure是View中final的方法,所以不能被子類重寫。通過標(biāo)志位mPrivateFlags去判斷是否布局必須要被重新測(cè)量,關(guān)于mPrivateFlags何時(shí)賦值變化有興趣可以找一下,這里就不闡述了。specChanged變量表明測(cè)量的條件有沒有與上次測(cè)量條件發(fā)生變化,isSpecExactly變量表明測(cè)量是否是確定的(測(cè)量條件是EXACTLY,不是AT_MOST或者UNSPECIFIED),matchesSpecSize變量表明寬高的測(cè)量大小是否與現(xiàn)有寬高相等,最后通過specChanged、isSpecExactly、matchesSpecSize、sAlwaysRemeasureExactly四個(gè)變量確定是否需要重新測(cè)量,sAlwaysRemeasureExactly變量故名思意總是需要重新測(cè)量(當(dāng)測(cè)量條件變化時(shí)),所以是否需要重新測(cè)量決定的因素是測(cè)量條件(widthMeasureSpec、heightMeasureSpec)發(fā)生變化且必須以下條件滿足其一:
1、必須要重新測(cè)量,sAlwaysRemeasureExactly置為true
2、測(cè)量條件不是EXACTLY
3、大小與現(xiàn)有大小不一致

當(dāng)滿足View必須要重新測(cè)量或者需要重新測(cè)量的條件下,進(jìn)入if條件,如果不是強(qiáng)制重新測(cè)量的話,會(huì)從mMeasureCache緩存池里面去取以前測(cè)量好的,當(dāng)存在以前測(cè)量好的話,直接setMeasuredDimensionRaw,測(cè)量結(jié)束,如果不存在的話,會(huì)走onMeasure方法,該方法一般都會(huì)被子類重寫。文章以FrameLayout為例,故現(xiàn)在看看FrameLayout中onMeasure的實(shí)現(xiàn)。如圖2,獲取當(dāng)前ViewGroup的子視圖,當(dāng)mMeasureAllChildren為true或者child.getVisibility() !=GONE(這就是為什么視圖為Gone時(shí)不進(jìn)行測(cè)量)時(shí)去測(cè)量每個(gè)子視圖的寬高,調(diào)用measureChildWithMargins。

如圖3,獲取子View的childWidthMeasureSpec與childHeightMeasureSpec

現(xiàn)以獲取子View寬度測(cè)量條件為例,如圖4,首先拿到父視圖specMode與specSize,根據(jù)父容器的specMode,做以下區(qū)分:
一、當(dāng)specMode為EXACTLY:
1、當(dāng)子View的childDimension>0,意思就是寫死的大小,此時(shí)子View的測(cè)量mode為EXACTLY,測(cè)量大小為子View寫死的大小
2、當(dāng)子View大小為L(zhǎng)ayoutParams.MATCH_PARENT,此時(shí)子View的測(cè)量mode為EXACTLY,大小為父容器的大小
3、當(dāng)子View的大小為L(zhǎng)ayoutParams.WRAP_CONTENT,測(cè)量mode為AT_MOST,大小為父容器的大小

二、當(dāng)specMode為AT_MOST:
1、當(dāng)子View的childDimension>0,意思就是寫死的大小,此時(shí)子View的測(cè)量mode為EXACTLY,測(cè)量大小為子View寫死的大小
2、當(dāng)子View大小為L(zhǎng)ayoutParams.MATCH_PARENT,此時(shí)子View的測(cè)量mode為AT_MOST,大小為父容器的大小
3、當(dāng)子View的大小為L(zhǎng)ayoutParams.WRAP_CONTENT,大小為父容器的大小,測(cè)量mode為AT_MOST
三,當(dāng)specMode為UNSPECIFIED這種情況很少見,故不做解析了
得到子View的測(cè)量條件后,調(diào)用child.measure(childWidthMeasureSpec, childHeightMeasureSpec),回到一開始,此時(shí)如果View沒有重新onMeasure的方法,將使用View的onMeasure方法,如圖5所示

如圖6所示,AT_MOST與EXACTLY最終拿的都是傳進(jìn)來的大小

這樣大家明白為什么自定View沒有重寫onMeasure時(shí),會(huì)將父布局充滿了吧,那為什么ImageView不會(huì)呢,我們看看ImageView中onMeasure方法

如圖7,當(dāng)mDrawable為空時(shí)resizeWidth、resizeHeight都為false,不考慮padding和SuggestedMinimum大小,w、h大小都為零。此時(shí)走到widthSize =resolveSizeAndState(w, widthMeasureSpec, 0)。如圖8,當(dāng)specMode為AT_MOST時(shí),此時(shí)specSize為父容器傳進(jìn)來的大小,size為剛剛計(jì)算的w=0,故此時(shí)大小為零,這樣ImageView就不會(huì)撐大整個(gè)父布局了

回到圖2 frameLayout的onMeasure方法,當(dāng)子View測(cè)量完了,會(huì)重新設(shè)置maxWidth、maxHeight,由于Frame Layout沒有子布局的相對(duì)關(guān)系,故最大寬、高基本都是每個(gè)子View寬高的對(duì)比,到此測(cè)量完成。但發(fā)現(xiàn)代碼沒完,我們繼續(xù)往下看,mMatchParentChildren size>1?
mMatchParentChildren 是什么呢,我們往上看
if (measureMatchParentChildren) {
????if (lp.width == LayoutParams.MATCH_PARENT ||
????????lp.height == LayoutParams.MATCH_PARENT) {
????????mMatchParentChildren.add(child);
? ? }
}
measureMatchParentChildren是在
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
意思就是當(dāng)父容器大小不固定時(shí),需要把子View寬為MATCH_PARENT 或者高為MATCH_PARENT 的View放到mMatchParentChildren的集合里面去,當(dāng)mMatchParentChildren大小大于1的時(shí)候,需要對(duì)這些子View重新測(cè)量,為什么這樣呢?
我們可以想想,當(dāng)有一個(gè)mMatchParentChildren里面的View重寫了onMeasure,此時(shí)大小不是父布局分配的大小,大于父布局分配的大小,此時(shí)父布局的大小等于該View的大小,而其他mMatchParentChildren里面View的大小是父布局分配的大小,那這時(shí)這些View就沒填充滿整個(gè)父布局,故需要重新去測(cè)量,將這個(gè)mMatchParentChildren里面的View賦予父布局的大小,這就完美解決了