view的大三流程開始之地在performTraversals過程中,而measure是三個流程中較為復(fù)雜的過程。而measure的開始地方在performTraversals中的代碼片段:
.....
//mStopped==true,該窗口activity處于停止?fàn)顟B(tài)
//mReportNextDraw,Window上報下一次繪制
if (!mStopped || mReportNextDraw) {
//觸摸模式發(fā)生了變化,且檢測焦點(diǎn)的控件發(fā)生了變化
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
//1. 焦點(diǎn)控件發(fā)生變化
//2. 窗口寬高測量值 != 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);
//------開始執(zhí)行測量操作--------
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
.....
可見在measure流程開始之前通過getRootMeasureSpec()獲取根布局的MeasureSpec并傳入performMeasure中開始measure流程。
MeasureSpec
MeasureSpec代表一個32位int值,高2位為SpecMode,低30位為SpecSize。在View中有MeasureSpec內(nèi)部類定義對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:父布局對View沒有任何約束,View可以是任何它想要的尺寸。
- EXACTLY:父布局精確設(shè)定了View的大小,無論子布局想要多大的都將得到父布局給予的精確邊界。這種情況實(shí)際上就對應(yīng)的是我們在xml文件或者LayoutParams中設(shè)置的xxdp/px或者M(jìn)ATH_PARENT這兩種情況,也就是精確的給予了布局邊界。
- AT_MOST:在確定的尺寸(父布局指定的SpecSize)內(nèi),View可以盡可能的大但不得超出SpecSize大小。這種情況對應(yīng)的就是我們在xml文件或者LayoutParams中設(shè)置的WRAP_CONTENT,子View可以隨著內(nèi)容的增加而申請更大的尺寸,但是不能超過指定的SpecSize。
在measure過程中,對于除了DecorView外的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的(還與View的margin和padding有關(guān));而對于DecorView來說,其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模式,且不可超過window大小
- default:MeasureSpec.EXACTLY模式,指定為DectorView的lp大小
View的MeasureSpec
View的mesure過程由ViewGroup傳遞。ViewGroup則相對復(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);
}
這就與上文說到的對于除了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模式,對應(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);
}
最終可以整理出一個表格,引用《Android開發(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一般我們在開發(fā)時不會用到故而不做分析。
measure
因?yàn)関iew通過measure即可完成測量過程,而ViewGroup還需要遍歷調(diào)用子view的measure方法。所以需要分別討論。
View的measure
View的measure由measure方法完成,而measure方法為final類型子類不可重寫。在measure方法中會調(diào)用onMeasure方法,且具體測量過程都是在該方法中完成。
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.無背景:使用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());
}
一般我們只會使用到AT_MOST、EXACTLY模式,但是需要注意的是當(dāng)為AT_MOST下默認(rèn)使用的是parentSize,即相當(dāng)于match_parent,所以自定義view要實(shí)現(xiàn)wrap_content時需要自己實(shí)現(xiàn)。
而使用UNSPECIFIED,則是
- 有背景,取背景大小和mMinWidth/mMinHeight 中最大值
- 無背景,取mMinWidth/mMinHeight
這樣子就確定了view的測量值,而view最終大小則由layout過程確定,一般情況兩者一致。
ViewGroup的measure
因?yàn)閂iewGroup除了完成自己的measure還需要遍歷子view的measure過程。而ViewGroup是個抽象類,提供了measureChlidren的方法,而對應(yīng)onMeasure則需要對應(yīng)實(shí)現(xiàn)的ViewGroup子類去實(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);//對每個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);
}
看見measureChildren時對Visibility==GONE的view不進(jìn)行measure操作。對于不同ViewGroup得分析網(wǎng)上已經(jīng)有很多分析了。這里稍微總結(jié)一下就是,
- LinearLayout(vertical):先測量所有子View大小,根據(jù)子View總高度和自身MeasureSpec得出剩余空間去分配使用weiget的view的大小,最后測量自身大小。
- RelativeLayout:根據(jù)依賴分別排序垂直和水平方向的view,并針對垂直和水平方向的view進(jìn)行measure(一次水平一次垂直),最后測量自身大小
- FrameLayout:測量子View,測量自身,如果自身為非精準(zhǔn)模式而子View為match_parent則這些子view需要再次測量。
測量完是不一定能拿到View的對應(yīng)測量大小的,因?yàn)橄到y(tǒng)在某些情況下可能進(jìn)行多次測量測能確定寬高。所以自定義view時最好在onLayout去獲取測量大小。外部獲取view大小可以通過:
- Activity/View#onWindowFocusChanged:這時view已經(jīng)初始化完成,但該方法可能多次被調(diào)用(獲取/失去焦點(diǎn)都會被調(diào)用)
- View#post:在Android Handler原理源碼淺析知道,當(dāng)執(zhí)行runnable時view已經(jīng)初始化完成(MainLooper已經(jīng)完成繪制去除了消息屏障)
- ViewTreeObserver:通過OnGlobalLayoutListener接口回調(diào)onGlobalLayout時view樹可見性已經(jīng)發(fā)生變化,這時去獲取view的寬高則可以(這個接口可能多次調(diào)用,回調(diào)后需取消監(jiān)聽)
LinearLayout Measure過程(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 跳過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());
//測量自身高度,方便計(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)有還有剩余空間,則針對對應(yīng)weight分配空間
.....
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//設(shè)置自身測量大小
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ù)依賴圖分別按垂直和水平排序子view
}
...
//根據(jù)水平方向依賴關(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ù)垂直方向依賴關(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;
}
....
}
}
//測量自身
....
setMeasuredDimension(width, height);
}
FrameLayout onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//是否需要測量match_parent的子view,當(dāng)為精準(zhǔn)模式不需要測量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) {
//測量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)列表,需要再次測量
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
.....
//測量自身大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
//自身非精準(zhǔn)模式,而子view為match_parent需要重新測量這些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開發(fā)藝術(shù)探索》
- View繪制流程及源碼解析(二)——onMeasure()流程分析