自定義
view
的流程分為measure
政钟、layout
、draw
三個主要步驟潮秘,今天我們通過源碼來分下下measure的過程我們從頂級view
開始琼开,頂級view
即DecorView
,view
的事件都是先經(jīng)過這個DecorView
,接下來我們來看看這個DecorView
的MeasureSpe
c的創(chuàng)建過程:
進入ViewRootImpl
中枕荞,查看measureHierarchy
方法柜候,有如下代碼:
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
這里只是截選一部分的源碼, 我們看到這個baseSize
躏精, 其實就是屏幕的尺寸大小渣刷, 獲取寬的MeasureSpc
的方法:
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
這里傳入的參數(shù)是屏幕尺寸以及DecorView自身的大小, 接著我們來看 getRootMeasureSpec
方法:
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;
}
就是這個方法確定了DecorView
的MeasureSpec
矗烛, 這里分了三種情況辅柴,
- 如果傳入的view大小為
math_parent
,那么這個view的mode為EXACTLY
瞭吃, 大小為屏幕的尺寸.
- 如果傳入的view大小為
wrap_content
碌嘀,那么這個view的mode為AT_MOST
,大小為屏幕的尺寸. - 如果傳入的view大小為一個具體的值,那么這個view的mode為
EXACTLY
虱而,大小為view本身大小筏餐。
以上就是DecorView的MeaureSpec
的整個創(chuàng)建的過程了。
看了頂級view之后我們來看普通的view牡拇, 普通的view的measure
過程是由viewgroup
傳遞過來的,接著我們來看看viewgroup
的measureChildWithMargins
方法:
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);
}
這個方法獲得了子view的MeasureSpec
穆律,并且將其傳入子view的measure
方法中惠呼, 這里重點來看下viewgroup
是如何創(chuàng)建子view的MeasuerSpec
的。來看getChildMeasureSpec
方法內(nèi)部的實現(xiàn):
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) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 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:
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);
}
這個方法很長峦耘,但是我們只需要注意到AT_MOST
跟EXACTLY
這兩種情況就行剔蹋,稍微分析下這個過程:
首先要理解這個方法的三個參數(shù), 第一個是父view的MeasureSpec
, 第二個是父view已占用的大小辅髓,第三個是view的LayoutParams
的大小泣崩,如果不理解可以看看ViewGroup
的MeasureChildWithMargins
方法中的調(diào)用:
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
第二個參數(shù)很長 ,
mPaddingLeft
+mPaddingRight
+lp.leftMargin
+lp.rightMargin
+withUsed
洛口,這些所有的值都有一個共同特點矫付,就是這些位置是不能擺放任何view的,即父view已經(jīng)占用的地盤第焰,現(xiàn)在是不是對參數(shù)更加理解了呢买优。
接著我們回到getChildMeasureSpec
方法中繼續(xù)看看viewGroup
到底是怎么創(chuàng)建view的MeasureSpec
的。
第一步: 根據(jù)參數(shù)一,即傳入的父view的MeasureSpec
獲得父view的Mode
和Size
杀赢。這里的第三行代碼:
int size = Math.max(0, specSize - padding);
這個size
表示取0與父容器中可占用的位置的最大值烘跺,可以直接理解為父view的大小。
第二步:根據(jù)父view的Mode分情況處理脂崔, 到這一步我們應(yīng)該就清楚為什么說view的大小是由父view的MeasureSpec
與本身LayoutParmas
大小共同決定的吧滤淳。
這里我們依然只看AT_MOST
跟EXACTLY
兩種情況,
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 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:
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;
這里有個比較難以理解的值就是childDimension
> 0 砌左,這個其實就表示view
的大小是一個具體的值比如100dp , 因為view的match_parent
和 wrap_content
在系統(tǒng)內(nèi)部定義的都是負數(shù)脖咐,一個是-1. 一個是-2 ,所以判斷childDimension
> 0即绊困,view的大小為一個具體的值文搂。
接著就比較好理解了, 我們來稍微總結(jié)下:
無論父view是
match_parent
還是wrap_content
秤朗,只要view是一個具體的值煤蹭,view的Mode永遠都是EXACTLY
, 大小均是view本身定義的大小。
父view模式如果是
EXACTLY
, ---> 子view如果是mathch_parent
取视,那么子view的大小是父view的大小硝皂,模式也跟父view一樣為EXACTLY
. 子view如果是wrap_content
, 大小還是父view的大小作谭, 模式為AT_MOST
父view模式如果是
AT_MOST
, --- > 子view如果是math_parent
稽物, 那么子view大小為父view大小, 模式與父view一樣都是AT_MOST
, 子view如果是wrap_content
折欠, 子view大小為父view大小贝或, 模式為AT_MOST
上面說的有點繞,但其實我們只需要記住一點锐秦, 無論上面那種情況咪奖,子view在wrap_content
下,大小都是父view的大小酱床, 到這里我們是不是就能理解為什么在自定義view的過程中如果不重寫onMeasure
羊赵, wrap_content
是和match_parent
是一個效果了吧。
以上過程是viewGroup中創(chuàng)建子view的
MeasureSpec
的過程扇谣,
有了這個MeasureSpec昧捷,測量子view大小就很簡單了,我們可以看到在ViewGroup獲取到子view的MeasureSpec
之后罐寨,傳入到子view的measure
方法中:
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
進入到view的measure
方法,
不知不覺我們已經(jīng)從viewgroup
進入到了view
的測量過程靡挥,
這里是不是突然意識到,ViewGroup
根本沒有測繪自己本身啊衩茸, 只是獲取到子view的MeasureSpec
然后傳入子view的measure
方法里去芹血,這是因為ViewGroup
是個抽象類贮泞,本身并沒有定義測量的過程, ViewGroup
的onMeasure
需要各個子類去實現(xiàn)幔烛,比如LinearLayout
啃擦、 RelativeLayout
等等,并且每個子類的測量過程都不一樣饿悬,這個我們后面會講令蛉, 現(xiàn)在我們還是接著看view的Measure
過程。
上面說到viewgroup
將創(chuàng)建的子view的MeasureSpec
傳入到了view的Measure
方法中狡恬, 那么我們就來看看View的Measure
方法:
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);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -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
}
這個方法真是又臭又長珠叔。。弟劲。講道理的話其實我也看不懂祷安, 但是我們只需要注意到一點, 就是這個方法調(diào)用了OnMeasure方法兔乞!
也就是說measure
-->OnMeasure
OnMeasure
就簡單了 :
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
很簡潔對不汇鞭,但是簡潔并不代表簡單, 這里套了好幾層庸追。霍骄。。 不要被迷惑 淡溯, 我們看最外層其實就是setMeasureDimension()
.
設(shè)置寬和高读整, 這個寬和高是在 getDefaultSize
方法里返回的, 所以我們來看看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;
}
如果我們忽略掉UNSPECIFIED
情況的話咱娶, 我們會發(fā)現(xiàn)第一個參數(shù)size
根本用不到米间。。膘侮。
也就是說view的大小其實就是父view給他創(chuàng)建的MeasureSpec
中的size大小车伞。
這也進一步說明,view在wrap_content
情況下 喻喳,大小還是會跟父view大小一樣, 所以我們需要在自定義view的時候重寫OnMeasure
困曙。