由於測量是一個從上到下的過程,而在這個過程中,父容器有必要告訴子View
它的一些繪製要求,那麼這時候就須要依賴一個信使,來傳遞這個要求,它就是MeasureSpec
. MeasureSpec
是一個32
位的int
類型,咱們把它分爲高2
位和低30
位。 其中高2
位表示mode
,它的取值爲:bash
UNSPECIFIED(0) : The parent has not imposed any constraint on the child. It can be whatever size it wants.
EXACTLY(1) : 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.
AT_MOST(2) : The child can be as large as it wants up to the specified size.
低30
位表示具體的size
。less
MeasureSpec
是父容器傳遞給子View
的寬高要求,並非說它傳遞的size
是多大,子View
最終就是多大,它是根據**父容器的MeasureSpec
和子View
的LayoutParams
**共同計算出來的。ide
爲了更好的理解上面這段話,咱們須要藉助ViewGroup
中的兩個函數:函數
measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
getChildMeasureSpec(int spec, int padding, int childDimension)
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);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
複製代碼
能夠看到,在調用getChildMeasureSpec
以前,須要考慮parent
和child
之間的間距,這包括parent
的padding
和child
的margin
,所以,參與傳遞給child
的MeasureSpec
的參數要考慮這麼幾方面:佈局
measureSpec
和padding
View
的height
和widht
以及margin
。下面咱們來分析getChildMeasureSpec
的具體流程,它對寬高的處理邏輯都是相同的,根據父容器measureSpec
的mode
,分紅如下幾種狀況:ui
mode
爲EXACTLY
這種狀況下說明父容器的大小已經肯定了,就是固定的值。this
View
指定了大小 那麼子View
的mode
就是EXACTLY
,size
就是佈局裏面的值,這裏就有疑問了,子View
所指定的寬高大於父容器的寬高怎麼辦呢?,咱們先留着這個疑問。View
爲MATCH_PARENT
子View
但願和父容器同樣大,由於父容器的大小是肯定的,因此子View
的大小也是肯定的,size
就是父容器measureSpec
的size
- 父容器的padding
- 子View``margin
。View
爲WRAP_CONTENT
子容器只要求可以包裹本身的內容,可是這時候它又不知道它所包裹的內容究竟是多大,那麼這時候它就指定本身的大小就不能超過父容器的大小,因此mode
爲AT_MOST
,size
和上面相似。mode
爲AT_MOST
在這種狀況下,父容器說明了本身最多不能超過多大,數值在measureSpec
的size
當中:spa
View
指定大小 同上分析。View
爲MATCH_PARENT
子View
但願和父容器同樣大,而此時父容器只知道本身不能超過多大,所以子View
也就只能知道本身不能超過多大,因此它的mode
爲AT_MOST
,size
就是父容器measureSpec
的size
- 父容器的padding
- 子View``margin
。View
爲WRAP_CONTENT
子容器只要求可以包裹本身的內容,可是這時候它又不知道它所包裹的內容究竟是多大,這時候雖然父容器沒有指定大小,可是它指定了最多不能超過多少,這時候子View
也不能超過這個值,因此mode
爲AT_MOST
,size
的計算和上面相似。mode
爲UNSPECIFIED
View
指定大小 同上分析。View
爲MATCH_PARENT
子View
但願和父容器同樣大,可是這時候父容器並無約束,因此子View
也是沒有約束的,因此它的mode
也爲UNSPECIFIED
,size
的計算和以前一致。View
爲WRAP_CONTENT
子View
不知道它包裹的內容多大,而且父容器是沒有約束的,那麼也只能爲UNSPECIFIED
了,size
的計算和以前一致。performTraversals()
介紹完了基礎的知識,咱們來從起點來整個看一下從View
樹的根節點到葉節點的整個測量的過程。 咱們先直接說明結論,整個測量的起點是在ViewRootImpl
的performTraversals()
當中:code
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//...
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼
上面的mView
是經過setView(View view, WindowManager.LayoutParams attrs, View panelParentView)
傳進來的,那麼這個view
是何時傳遞進來的呢? 如今回憶一下,在ActivityThread
的handleResumeActivity
中,咱們調用了ViewManager.add(mDecorView, xxx)
,而這個方法最終會調用到WindowManagerGlobal
的下面這個方法:orm
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
root = new ViewRootImpl(view.getContext(), display);
}
//....
root.setView(view, wparams, panelParentView);
}
複製代碼
也就是說,上面的**mView
也就是咱們在setContentView
當中渲染出來的mDecorView
**,也就是說它是整個View
樹的根節點,由於mDecorView
是一個FrameLayout
,因此它調用的是FrameLayout
的measure
方法。 那麼這整個從根節點遍歷完整個View
樹的過程是怎麼實現的呢? 它其實就是依賴於measure
和onMeasure
:
View
,measure
是在它裏面定義的,並且它是一個final
方法,所以它的全部子類都沒有辦法重寫該方法,在該方法當中,會調用onMeasure
來設置最終測量的結果,對於View
來講,它只是簡單的取出父容器傳進來的要求來設置,並無複雜的邏輯。public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
複製代碼
ViewGroup
,因爲它是View
的子類,所以它不可能重寫measure
方法,而且它也沒有重寫onMeasure
方法。View
的控件,例如TextView
,它會重寫onMeasure
,與View#onMeasure
不一樣的是,它會考慮更多的狀況來決定最終的測量結果。ViewGroup
的控件,例如FrameLayout
,它一樣會重寫onMeasure
方法,與繼承於View
的控件不一樣的是,因爲ViewGroup
可能會有子View
,所以它在設置本身最終的測量結果以前,還有一個重要的任務:調用子View
的measure
方法,來對子View
進行測量,並根據子View
的結果來決定本身的大小。所以,整個從上到下的測量,其實就是一個View
樹節點的遍歷過程,每一個節點的onMeasure
返回時,就標誌它的測量結束了,而這整個的過程是以View
中measure
方法爲紐帶的:
mDecorView
這個根節點的measure
方法,也就是performTraversals
中的那句話。ViewGroup
的控件,那麼在它的onMeasure
方法中,它並不會直接調用子節點的onMeasure
方法,而是經過調用子節點measure
方法,因爲子節點不可能重寫View#measure
方法,所以它最終是經過View#measure
來調用子節點重寫的onMeasure
來進行測量,子節點再在其中進行響應的邏輯處理。onMeausre
方法被調用時,它須要設置好本身的測量結果就好了。對於measure
和onMeasure
的區別,咱們能夠用一句簡單的話來總結一下:measure
負責進行測量的傳遞,onMeasure
負責測量的具體實現。
onMeasure
當中的setMeasuredDimension
上面咱們講到設置的測量結果,其實測量過程的最終目的是:經過調用setMeasuredDimension
方法來給mMeasureHeight
和mMeasureWidth
賦值。 只要上面這個過程完成了,那麼該ViewGroup/View/及其實現類
的測量也就結束了,而**setMeasuredDimension
必須在onMeasure
當中調用,不然會拋出異常**,因此咱們觀察全部繼承於ViewGroup/View
的控件,都會發現它們最後都是調用上面說的那個方法。 前面咱們已經分析過,measure
只是傳遞的紐帶,所以它的邏輯是固定的,咱們直接看各個類的onMeasure
方法就好。
View
的onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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;
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
複製代碼
這裏,咱們會根據前面所說的,父容器傳遞進來measureSpec
中的mode
來給這兩個變量賦值:
mode
爲UNSPECIFIED
,那麼說明父容器並不期望多個,所以子View
根據本身的背景或者minHeight/minWidth
屬性來給本身賦值。AT_MOST
或者EXACTLY
,那麼就把它設置爲父容器指定的size
。ViewGroup
的onMeasure
因爲ViewGroup
的目的是爲了容納各子View
,可是它並不肯定子View
應當如何排列,也就不知道該如何測量本身,所以它的onMeasure
是沒有任何意義的,因此並無重寫,而是應當由繼承於它的控件來重寫該方法。
ViewGroup
控件的onMeasure
爲了方面,咱們以DecorView
爲例,通過前面的分析,咱們知道當咱們在performTraversals
中調用它的measure
方法時,最終會回調到它對應的控件類型,也就是FrameLayout
的onMeasure
方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
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) {
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());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); 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); } } } 複製代碼
咱們能夠看到,整個的onMeasure
其實分爲三步:
View
,調用measureChildWithMargins
進行第一次子View
的測量,在第一節中,咱們也分析了這個方法,它最終也是調用子View
的measure
方法。setMeasuredDimension
來設置本身的測量結果。View
,根據第二步的結果,調用child.measure
進行第二次的測量。這也驗證了第二節中的結論:父容器和子View
的關聯是經過measure
進行關聯的。 同時咱們也能夠有一個新的結論,對於View
樹的某個節點,它的測量結果有可能並非一次決定的,這是因爲父容器可能須要依賴於子View
的測量結果,而父容器的結果又可能會影響子View
,可是,咱們須要保證這個過程不是無限調用的。