view的大三流程開(kāi)始之地在performTraversals過(guò)程中栅受,而measure是三個(gè)流程中較為復(fù)雜的過(guò)程侵佃。而measure的開(kāi)始地方在performTraversals中的代碼片段:
.....
//mStopped==true,該窗口activity處于停止?fàn)顟B(tài)
//mReportNextDraw,Window上報(bào)下一次繪制
if (!mStopped || mReportNextDraw) {
//觸摸模式發(fā)生了變化,且檢測(cè)焦點(diǎn)的控件發(fā)生了變化
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
//1. 焦點(diǎn)控件發(fā)生變化
//2. 窗口寬高測(cè)量值 檐春!= WMS計(jì)算的mWinFrame寬高
//3. contentInsetsChanged==true顽聂,邊襯區(qū)域發(fā)生變化
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//------開(kāi)始執(zhí)行測(cè)量操作--------
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
.....
可見(jiàn)在measure流程開(kāi)始之前通過(guò)getRootMeasureSpec()獲取根布局的MeasureSpec并傳入performMeasure中開(kāi)始measure流程肥惭。
MeasureSpec
MeasureSpec代表一個(gè)32位int值,高2位為SpecMode紊搪,低30位為SpecSize蜜葱。在View中有MeasureSpec內(nèi)部類(lèi)定義對(duì)MeasureSpec的相關(guān)處理及常量定義。
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: 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.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
// Creates a measure specification based on the supplied size and mode.
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
SpecMode有三種情況:
- UNSPECIFIED:父布局對(duì)View沒(méi)有任何約束耀石,View可以是任何它想要的尺寸牵囤。
- EXACTLY:父布局精確設(shè)定了View的大小,無(wú)論子布局想要多大的都將得到父布局給予的精確邊界滞伟。這種情況實(shí)際上就對(duì)應(yīng)的是我們?cè)趚ml文件或者LayoutParams中設(shè)置的xxdp/px或者M(jìn)ATH_PARENT這兩種情況揭鳞,也就是精確的給予了布局邊界。
- AT_MOST:在確定的尺寸(父布局指定的SpecSize)內(nèi)诗良,View可以盡可能的大但不得超出SpecSize大小汹桦。這種情況對(duì)應(yīng)的就是我們?cè)趚ml文件或者LayoutParams中設(shè)置的WRAP_CONTENT,子View可以隨著內(nèi)容的增加而申請(qǐng)更大的尺寸,但是不能超過(guò)指定的SpecSize鉴裹。
在measure過(guò)程中舞骆,對(duì)于除了DecorView外的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的(還與View的margin和padding有關(guān))径荔;而對(duì)于DecorView來(lái)說(shuō)督禽,其MeasureSpec由它自身的LayoutParams決定。
DecorView的MeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
因?yàn)镈ectorView的MeasureSpec只與自身LayoutParams有關(guān)总处”繁梗可以分為三種情況:
- LayoutParams.MATH_PARENT:MeasureSpec.EXACTLY模式,強(qiáng)制DectorView大小與window大小一致
- LayoutParams.WRAP_CONTENT:MeasureSpec.AT_MOST模式,且不可超過(guò)window大小
- default:MeasureSpec.EXACTLY模式胧谈,指定為DectorView的lp大小
View的MeasureSpec
View的mesure過(guò)程由ViewGroup傳遞忆肾。ViewGroup則相對(duì)復(fù)雜一些。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (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);
}
這就與上文說(shuō)到的對(duì)于除了DecorView外的View菱肖,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的(還與View的margin和padding有關(guān))客冈。
子View的MeasureSpec創(chuàng)建:與父容器的MeasureSpec和自身的LayoutParams有關(guān),此外還和View的margin及padding有關(guān)稳强。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//子View可用空間
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY: //父布局為EXACTLY模式场仲,對(duì)應(yīng)match_parent及dp/px
if ( >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST: //父布局為AT_MOST模式,
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
最終可以整理出一個(gè)表格退疫,引用《Android開(kāi)發(fā)藝術(shù)探索》中的表圖渠缕。
稍微整理則是:
- 當(dāng)子view指定dp/px :使用EXACTLY模式,并遵循LayoutParams大小
- 當(dāng)子view寬/高為match_parent :
- 父布局為EXACTLY褒繁,則子view為EXACTLY亦鳞,且大小為parent可用大小
- 父布局為AT_MOST,則子view為AT_MOST澜汤,且大小為parent可用大小
- 當(dāng)子view寬/高為wrap_content:使用AT_MOST蚜迅,且大小為parent可用大小
而UNSPECIFIED一般我們?cè)陂_(kāi)發(fā)時(shí)不會(huì)用到故而不做分析。
measure
因?yàn)関iew通過(guò)measure即可完成測(cè)量過(guò)程俊抵,而ViewGroup還需要遍歷調(diào)用子view的measure方法谁不。所以需要分別討論。
View的measure
View的measure由measure方法完成徽诲,而measure方法為final類(lèi)型子類(lèi)不可重寫(xiě)刹帕。在measure方法中會(huì)調(diào)用onMeasure方法,且具體測(cè)量過(guò)程都是在該方法中完成谎替。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//1.AT_MOST偷溺、EXACTLY情況下返回MeasureSpec中的Size
//2.UNSPECIFIED使用getSuggestedMinimumXX的返回值
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;
}
//1.無(wú)背景:使用android:minWidth(mMinWidth)的值(可為0)
//2.有背景:取背景大小或mMinWidth中的 最大值
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
一般我們只會(huì)使用到AT_MOST、EXACTLY模式钱贯,但是需要注意的是當(dāng)為AT_MOST下默認(rèn)使用的是parentSize挫掏,即相當(dāng)于match_parent,所以自定義view要實(shí)現(xiàn)wrap_content時(shí)需要自己實(shí)現(xiàn)秩命。
而使用UNSPECIFIED尉共,則是
- 有背景,取背景大小和mMinWidth/mMinHeight 中最大值
- 無(wú)背景弃锐,取mMinWidth/mMinHeight
這樣子就確定了view的測(cè)量值袄友,而view最終大小則由layout過(guò)程確定,一般情況兩者一致霹菊。
ViewGroup的measure
因?yàn)閂iewGroup除了完成自己的measure還需要遍歷子view的measure過(guò)程剧蚣。而ViewGroup是個(gè)抽象類(lèi),提供了measureChlidren的方法,而對(duì)應(yīng)onMeasure則需要對(duì)應(yīng)實(shí)現(xiàn)的ViewGroup子類(lèi)去實(shí)現(xiàn)了鸠按。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);//對(duì)每個(gè)children進(jìn)行measure
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//getChildMeasureSpec可看上文的MeasureSpec解析
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
看見(jiàn)measureChildren時(shí)對(duì)Visibility==GONE的view不進(jìn)行measure操作礼搁。對(duì)于不同ViewGroup得分析網(wǎng)上已經(jīng)有很多分析了。這里稍微總結(jié)一下就是目尖,
- LinearLayout(vertical):先測(cè)量所有子View大小叹坦,根據(jù)子View總高度和自身MeasureSpec得出剩余空間去分配使用weiget的view的大小,最后測(cè)量自身大小卑雁。
- RelativeLayout:根據(jù)依賴(lài)分別排序垂直和水平方向的view,并針對(duì)垂直和水平方向的view進(jìn)行measure(一次水平一次垂直)绪囱,最后測(cè)量自身大小
- FrameLayout:測(cè)量子View测蹲,測(cè)量自身,如果自身為非精準(zhǔn)模式而子View為match_parent則這些子view需要再次測(cè)量鬼吵。
測(cè)量完是不一定能拿到View的對(duì)應(yīng)測(cè)量大小的扣甲,因?yàn)橄到y(tǒng)在某些情況下可能進(jìn)行多次測(cè)量測(cè)能確定寬高。所以自定義view時(shí)最好在onLayout去獲取測(cè)量大小齿椅。外部獲取view大小可以通過(guò):
- Activity/View#onWindowFocusChanged:這時(shí)view已經(jīng)初始化完成琉挖,但該方法可能多次被調(diào)用(獲取/失去焦點(diǎn)都會(huì)被調(diào)用)
- View#post:在Android Handler原理源碼淺析知道,當(dāng)執(zhí)行runnable時(shí)view已經(jīng)初始化完成(MainLooper已經(jīng)完成繪制去除了消息屏障)
- ViewTreeObserver:通過(guò)OnGlobalLayoutListener接口回調(diào)onGlobalLayout時(shí)view樹(shù)可見(jiàn)性已經(jīng)發(fā)生變化涣脚,這時(shí)去獲取view的寬高則可以(這個(gè)接口可能多次調(diào)用示辈,回調(diào)后需取消監(jiān)聽(tīng))
LinearLayout Measure過(guò)程(Vertical)
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
//null 或者 GONE 跳過(guò)measure
......
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
//精準(zhǔn)模式直接記錄children高度
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
// The heightMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal height. We'll restore the original height of 0
// after measurement.
lp.height = LayoutParams.WRAP_CONTENT;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
//measure子view高度
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
//記錄children高度
.....
}
.....
}
......
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
//測(cè)量自身高度,方便計(jì)算是否還是有剩余高度
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
//當(dāng)有還有剩余空間遣蚀,則針對(duì)對(duì)應(yīng)weight分配空間
.....
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//設(shè)置自身測(cè)量大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
RelativeLayout onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mDirtyHierarchy) {
mDirtyHierarchy = false;
sortChildren();//根據(jù)依賴(lài)圖分別按垂直和水平排序子view
}
...
//根據(jù)水平方向依賴(lài)關(guān)系矾麻,measure子view
View[] views = mSortedHorizontalChildren;
int count = views.length;
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);
applyHorizontalSizeRules(params, myWidth, rules);
measureChildHorizontal(child, params, myWidth, myHeight);
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
}
//根據(jù)垂直方向依賴(lài)關(guān)系,measure子view
views = mSortedVerticalChildren;
count = views.length;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
applyVerticalSizeRules(params, myHeight, child.getBaseline());
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}
....
}
}
//測(cè)量自身
....
setMeasuredDimension(width, height);
}
FrameLayout onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//是否需要測(cè)量match_parent的子view芭梯,當(dāng)為精準(zhǔn)模式不需要測(cè)量match_parent的子view
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//測(cè)量ziview
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
//將match_parent子view添加進(jìn)列表险耀,需要再次測(cè)量
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
.....
//測(cè)量自身大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
//自身非精準(zhǔn)模式,而子view為match_parent需要重新測(cè)量這些view
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
關(guān)于ConstraintLayout的measure解析玖喘,日后補(bǔ)上
參考:
- 《Android開(kāi)發(fā)藝術(shù)探索》
- View繪制流程及源碼解析(二)——onMeasure()流程分析