本篇文章主要分析View和ViewGroup的measure過程, 由于ViewGroup還可以包含子元素, 所以相對(duì)于View來說會(huì)有幾個(gè)對(duì)子View measure的方法, 下面講分別分析。
概述
View和ViewGroup的工作流程主要指measure、layout、draw這三大流程, 即測(cè)量、布局和繪制。measure流程用來測(cè)量View和ViewGroup所占矩形區(qū)域的大小, 即測(cè)量寬高(最終寬高可能會(huì)因?yàn)閘ayout過程而改變)。layout流程確定View和ViewGroup的最終寬高和矩形區(qū)域在父布局中的位置坐標(biāo)(父布局再一層層往上, 最終會(huì)是確定了在屏幕中的位置坐標(biāo))。draw流程則是講View和ViewGroup繪制在屏幕上(底層調(diào)用SurfaceFlinger進(jìn)行繪制, 這里不進(jìn)行分析)。
View 的measure過程
首先看一下android 7.0 中View.java中的measure()方法如下,被定義成了final的,View的子類將不能重寫此方法。方法文檔寫到:調(diào)用該方法用來查明一個(gè)view應(yīng)該是多大,該view所在的父容器提供寬高參數(shù)的約束信息。實(shí)際的measure工作在onMeasure方法中進(jìn)行,因此只有onMeasure方法可以被子類重寫。
/**
* <p>
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* </p>
* <p>
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* </p>
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 判斷當(dāng)前view的LayoutMode是否為opticalbounds
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) { // 判斷當(dāng)前view的父容器的LayoutMode是否為opticalbounds
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
// 根據(jù)我們傳入的widthMeasureSpec和heightMeasureSpec計(jì)算key值,我們?cè)趍MeasureCache中存儲(chǔ)我們view的信息
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
// 如果mMeasureCache為null,則進(jìn)行new一個(gè)對(duì)象
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
// mOldWidthMeasureSpec和mOldHeightMeasureSpec分別表示上次對(duì)View進(jìn)行量算時(shí)的widthMeasureSpec和heightMeasureSpec
// 執(zhí)行View的measure方法時(shí),View總是先檢查一下是不是真的有必要費(fèi)很大力氣去做真正的量算工作
// mPrivateFlags是一個(gè)Int類型的值,其記錄了View的各種狀態(tài)位
// 如果(mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT,
// 那么表示當(dāng)前View需要強(qiáng)制進(jìn)行l(wèi)ayout(比如執(zhí)行了View的forceLayout方法),所以這種情況下要嘗試進(jìn)行量算
// 如果新傳入的widthMeasureSpec/heightMeasureSpec與上次量算時(shí)的mOldWidthMeasureSpec/mOldHeightMeasureSpec不等,
// 那么也就是說該View的父ViewGroup對(duì)該View的尺寸的限制情況有變化,這種情況下要嘗試進(jìn)行量算
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
// 通過運(yùn)算,重置mPrivateFlags值,即View的測(cè)量狀態(tài)
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
// 解決布局中的Rtl問題
resolveRtlPropertiesIfNeeded();
// 判斷當(dāng)前View是否是強(qiáng)制進(jìn)行測(cè)量,如果是則將cacheIndex=-1,反之從mMeasureCache中獲取
// 對(duì)應(yīng)的index,即從緩存中讀取存儲(chǔ)的大小。
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
// 根據(jù)cacheIndex的大小判斷是否需要重新測(cè)量,或者根據(jù)布爾變量sIgnoreMeasureCache進(jìn)行判斷。
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
// 重新測(cè)量,則調(diào)用我們重寫的onMeasure()方法進(jìn)行測(cè)量,然后重置View的狀態(tài)
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
// 通過我們計(jì)算的cacheIndex值,從緩存中讀取我們的測(cè)量值。
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
// 通過setMeasuredDimension()方法設(shè)置我們的測(cè)量值,然后重置View的狀態(tài)
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
// 如果View的狀態(tài)沒有改變,則會(huì)拋出異常"我們沒有調(diào)用”setMeasuredDimension()"方法,一般出現(xiàn)在我們重寫onMeasure方法,
// 但是沒有調(diào)用setMeasuredDimension方法導(dǎo)致的。
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;
// 將最新的widthMeasureSpec和heightMeasureSpec進(jìn)行存儲(chǔ)到mMeasureCache
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
上面的measure方法大致的描述了測(cè)量的流程。
- 1 首先判斷當(dāng)前View的layoutMode模式,通過調(diào)用isLayoutModeOptical方法進(jìn)行判斷。這個(gè)方法就是判斷view是否為ViewGroup類型,然后判斷l(xiāng)ayoutMode設(shè)定是否為opticalBounds。如果是,則對(duì)傳入的widthMeasureSpec、heightMeasureSpec進(jìn)行重新計(jì)算封裝。
- 2 判斷當(dāng)前view是否強(qiáng)制重新計(jì)算,或者傳入進(jìn)來的MeasureSpec是否和上次不同。這兩種情況滿足一種則進(jìn)行測(cè)量運(yùn)算。
- 3 如果為強(qiáng)制測(cè)量或者忽略緩存,則調(diào)用onMeasure()方法進(jìn)行測(cè)量,反之,從mMeasureCache緩存中讀取上次的測(cè)量數(shù)據(jù)。
- 4 將最新的widthMeasureSpec和heightMeasureSpec進(jìn)行存儲(chǔ)到mMeasureCache。
上面這段代碼主要涉及到3個(gè)主要的地方:
- 1 android:layoutMode屬性的判斷,如果是opticalBounds則需要重新計(jì)算MeasureSpec。
- 2 MeasureSpec的概念。
- 3 onMeasure方法。
ViewGroup的android:layoutMode屬性
android:layoutMode屬性我們很少用的到,設(shè)置layoutMode屬性有兩個(gè)常量值,默認(rèn)是clipBounds,clipBounds 表示了getLeft()、getRight()等返回的原始值組成的邊界;opticalBounds描述了視覺上的邊界,視覺邊界位于Clip bounds中,Clip bounds會(huì)更大,因?yàn)橛脕盹@示一些其他效果比如陰影和閃光等。比如Button,它的clipBounds比opticalBounds大一圈。

/**
* This constant is a {@link #setLayoutMode(int) layoutMode}.
* Clip bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
* {@link #getRight() right} and {@link #getBottom() bottom}.
*/
public static final int LAYOUT_MODE_CLIP_BOUNDS = 0;
/**
* This constant is a {@link #setLayoutMode(int) layoutMode}.
* Optical bounds describe where a widget appears to be. They sit inside the clip
* bounds which need to cover a larger area to allow other effects,
* such as shadows and glows, to be drawn.
*/
public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;

View.java中的isLayoutModeOptical()方法
/**
* Return true if o is a ViewGroup that is laying out using optical bounds.
* @hide
*/
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
ViewGroup.java中的isLayoutModeOptical() 方法
/** Return true if this ViewGroup is laying out using optical bounds. */
boolean isLayoutModeOptical() {
return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
}
MeasureSpec的概念
measure()方法中傳的是MeasureSpec變量,在View測(cè)量的時(shí)候,系統(tǒng)會(huì)將該View的LayoutParams在父容器的約束(父容器的MeasureSpec)下轉(zhuǎn)換成對(duì)應(yīng)的MeasureSpec,然后再根據(jù)這個(gè)MeasureSpec來確定View的測(cè)量寬高。它是View的一個(gè)內(nèi)部類,代表了一個(gè)32位int值,高2位代表SpecMode,即測(cè)量模式。低30位代表SpecSize,即在該SpecMode下面的規(guī)格大小。
SpecMode有三種模式:
- 1 UNSPECIFIED(未指定),父容器不對(duì)子元素施加任何束縛,子元素可以得到任意想要的大小,一般用于系統(tǒng)內(nèi)部,標(biāo)識(shí)一種測(cè)量的狀態(tài)。
- 2 EXACTLY(精準(zhǔn)),父容器已經(jīng)檢測(cè)出子元素所需要的確切大小,這個(gè)時(shí)候子元素的最終大小就是SpecSize所指定的值。它對(duì)應(yīng)于LayoutParams中的match_parent和具體的數(shù)值這兩種模式。
- 3 AT_MOST(至多),父容器指定了一個(gè)可用大小,即SpecSize,子元素的大小不能大于這個(gè)值,具體是什么要看不同子元素的具體實(shí)現(xiàn),它對(duì)應(yīng)于LayoutParams中的wrap_content。
MeasureSpec的部分源碼:
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
* A MeasureSpec is comprised of a size and a mode.
*/
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* 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;
/**
* 根據(jù)specSize和specMode打包成MeasureSpec
*/
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
* 根據(jù)MeasureSpec拆分出specMode
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
* 根據(jù)MeasureSpec拆分出specSize
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
...
}
onMeasure方法
onMeasure方法測(cè)量View和它的內(nèi)容來決定測(cè)量寬高,該方法被measure方法調(diào)用,它應(yīng)該被子類重寫用來提供精準(zhǔn)高效的測(cè)量。
/**
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overridden by subclasses to provide accurate and efficient
* measurement of their contents.
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension()方法用來存儲(chǔ)測(cè)量后的寬高,調(diào)用此方法完成后,測(cè)量過程就已經(jīng)結(jié)束了,這之后可以拿到測(cè)量后的寬高值。
/**
* <p>This method must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.</p>
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
在onMeasure方法調(diào)用setMeasuredDimension()時(shí), setMeasuredDimension()中傳入的是getDefaultSize()的返回值,看一下getDefaultSize()方法:
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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;
}
可以看出getDefaultSize()方法的邏輯很簡(jiǎn)單,我們只需要看AT_MOST和EXACTLY兩種情況,getDefaultSize()返回的就是measureSpec的specSize ,而這傳入的measureSpec是在父容器中由父容器的measureSpec和當(dāng)前子元素的LayoutParams已經(jīng)共同確定的,所以由measureSpec拆分出來的specSize 就是子元素測(cè)量后的大小。針對(duì)UNSPECIFIED的情況,一般用于系統(tǒng)內(nèi)部的測(cè)量過程,下面以分析getSuggestedMinimumWidth()為例分析該函數(shù)的返回值意義:
/**
* Returns the suggested minimum width that the view should use. This
* returns the maximum of the view's minimum width)
* and the background's minimum width
* ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
*
* @return The suggested minimum width of the view.
*/
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
可以看出,如果View沒有設(shè)置背景,那么View的寬度即為mMinWidth ,而mMinWidth 對(duì)應(yīng)于android:mMinWidth 這個(gè)屬性的值,如果沒有設(shè)置該屬性則mMinWidth 默認(rèn)為0;如果View指定了背景。則View的寬度為max(mMinWidth, mBackground.getMinimumWidth()),那么mBackground.getMinimumWidth()是什么呢?看一下Drawable的getMinimumWidth()方法:
/**
* Returns the minimum width suggested by this Drawable. If a View uses this
* Drawable as a background, it is suggested that the View use at least this
* value for its width. (There will be some scenarios where this will not be
* possible.) This value should INCLUDE any padding.
*
* @return The minimum width suggested by this Drawable. If this Drawable
* doesn't have a suggested minimum width, 0 is returned.
*/
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
可以看出,getMinimumWidth()返回的就是Drawable的原始寬度,如果沒有原始寬度就返回0??偨Y(jié)一下getSuggestedMinimumWidth()的主要工作:如果View沒有設(shè)置背景就返回android:mMinWidth 這個(gè)屬性的值,可以人為設(shè)置該值,默認(rèn)為0;如果View設(shè)置了背景,則返回mMinWidth和背景最小寬度兩者中的最大值。getSuggestedMinimumWidth()的返回值就是View在UNSPECIFIED情況下的測(cè)量寬。
直接繼承自View的自定義控件如果在xml布局中設(shè)置了wrap_content屬性,則會(huì)實(shí)際表現(xiàn)起來相當(dāng)于match_parent。從getDefaultSize()方法可以看出,當(dāng)View設(shè)置wrap_content屬性時(shí),會(huì)是MeasureSpec.AT_MOST模式,和match_parent一樣,所以需要給改自定義View設(shè)置默認(rèn)的寬高值以表現(xiàn)出wrap_content的效果。TextView針對(duì)wrap_content進(jìn)行了特殊處理,在TextView的onMeasure方法中傳入的MeasureSpec中的specSize重新計(jì)算了一遍,并沒有使用從父容器傳來的specSize(該specSize指父容器所能提供的最大空間,和match_parent一樣的效果)。
說到最后,可能會(huì)有疑問,頂層View--DecorView的MeasureSpec是如何獲得的呢?因?yàn)樽釉氐臏y(cè)量需要父容器的MeasureSpec和自身的LayoutParams共同決定(這點(diǎn)在ViewGroup的測(cè)量過程中能看出,接下來分析),所以頂層父容器DecorView的MeasureSpec必須最先獲取,然后才能開始遍歷子元素的測(cè)量工作。
分析Activity的setContentView方法可知,DecorView在PhoneWindow中被創(chuàng)建,在Activity的onResume方法回調(diào)的時(shí)候繼而調(diào)用Activity的makeVisible()方法,Activity對(duì)應(yīng)的PhoneWindow對(duì)象才被WindowManager添加。此后開始View的三大流程。
// ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
...
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
}
// Activity.java的makeVisible()方法:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
之后:
WindowManager.addView()->WindowManagerImpl.addView()->WindowManagerGlobal.addView()->創(chuàng)建ViewRootImpl對(duì)象->ViewRootImpl.setView(DecorView對(duì)象)->ViewRootImpl.requestLayout()->ViewRootImpl.performTraversals()->然后觸發(fā)performMeasure()、performLayout()、performDraw()。
WindowManagerGlobal.addView()核心代碼:
...
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
root.setView(view, wparams, panelParentView);
...
}
先說結(jié)論:DecorView的MeasureSpec是由窗口的尺寸(PhoneWindow)和其自身的LayoutParams共同決定的,(對(duì)于普通View,MeasureSpec是父容器的MeasureSpec和其自身的LayoutParams共同決定),MeasureSpec一旦確定,onMeasure中就可以確定View的測(cè)量寬高。
對(duì)于DecorView,在ViewRootImpl的measureHierarchy方法計(jì)算出DecorView的MeasureSpec值,如下:
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
...
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
其中desiredWindowWidth、desiredWindowHeight分別對(duì)應(yīng)著Window的寬高(屏幕的尺寸)。下面看一下getRootMeasureSpec()方法:
/**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
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;
}
- 1 WindowManager.LayoutParams.MATCH_PARENT:精準(zhǔn)模式,SpecSize就是窗口的大小。
- 2 WindowManager.LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超過窗口的大小。
- 3 固定大?。壕珳?zhǔn)模式,大小為L(zhǎng)ayoutParams中指定的值。
頂層DecorView的MeasureSpec確定后,開始遍歷遞歸子元素進(jìn)行測(cè)量流程,然后如果遇到了View就是上面分析的測(cè)量流程,如果遇到的是ViewGroup則接下來進(jìn)行分析。
ViewGroup 的measure過程
ViewGroup繼承自View,View中的measure方法由于final不能被重新,onMeasure也沒有重寫。ViewGroup 的measure情況分為兩種,一個(gè)是measure自己,另一個(gè)是measure子元素。但是ViewGroup并沒有定義具體的measure自己的過程,其測(cè)量過程的onMeasure需要由各個(gè)具體的子類去實(shí)現(xiàn),比如LinearLayout、RelativeLayout,因?yàn)椴煌腣iewGroup子類有不同的布局特性。
ViewGroup measure子元素過程有measureChildren()方法如下:
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
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);
}
}
}
其內(nèi)部通過循環(huán)遍歷所有的子元素,對(duì)可見的子元素再調(diào)用measureChild()方法:
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChildWithMargins()方法也類似,在調(diào)用getChildMeasureSpec()方法獲取子元素的MeasureSpec中增加了子元素的Margin值。measureChild()函數(shù)內(nèi)部清晰的表達(dá)了子元素 MeasureSpec的產(chǎn)生過程:先得到子元素的LayoutParams,然后調(diào)用getChildMeasureSpec()方法將父容器的parentMeasureSpec與padding值、子元素的LayoutParams一起合成了子元素的MeasureSpec。getChildMeasureSpec()方法如下:
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
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);
}
上面代碼的主要邏輯是:如果子元素的LayoutParams中寬高是具體精準(zhǔn)數(shù)值時(shí),不管父容器的 MeasureSpec是什么,子元素的MeasureSpec都是精準(zhǔn)模式切大小遵循LayoutParams中的具體數(shù)值寬高;如果子元素的LayoutParams中寬高是match_parent,當(dāng)父容器是精準(zhǔn)模式時(shí),子元素也是精準(zhǔn)模式并且大小不會(huì)超過父容器剩余空間大小,當(dāng)父容器是最大模式時(shí),子元素也是最大模式并且其大小不會(huì)超過父容器剩余空間;如果子元素的LayoutParams中寬高是wrap_content時(shí),不管父容器是精準(zhǔn)模式還是最大化,子元素的模式總是最大化且不能超過父容器的剩余空間。
以LinearLayout分析ViewGroup的measure自己過程
LinearLayout的onMeasure()方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
看得到有垂直測(cè)量和水平測(cè)量?jī)煞N,下面分析 measureVertical()方法的大致邏輯:
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
...
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,heightMeasureSpec, usedHeight);
...
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
...
從上面這段代碼可以看出,會(huì)遍歷子元素并對(duì)每個(gè)子元素執(zhí)行measureChildBeforeLayout()方法,這個(gè)方法內(nèi)部會(huì)調(diào)用子元素的measure()方法,然后各個(gè)子元素開始一次進(jìn)入measure過程,系統(tǒng)通過mTotalLength來存儲(chǔ)LinearLayout在豎直方向的初步高度。每測(cè)量一個(gè)元素,mTotalLength就會(huì)增加,增加的部分包括子元素的高度以及子元素在豎直方向的margin等,當(dāng)子元素測(cè)量完畢后,LinearLayout會(huì)測(cè)量自己的大?。?/p>
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
...
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);
如果LinearLayout在布局中指定的高度是wrap_content,那么LinearLayout的高度是等有所子元素測(cè)量完畢后子元素所占用的高度總和,但是仍然不能超過LinearLayout的父容器的剩余空間,還需要考慮豎直方向的pading。resolveSizeAndState()函數(shù)主要是完成最后的測(cè)量。
獲取View寬高的時(shí)機(jī)
- 1 Activity/View的onWindowFocusChanged.View已經(jīng)初始化完畢,寬高準(zhǔn)備好了,onWindowFocusChanged會(huì)被調(diào)用多次,當(dāng)Activity的窗口得到焦點(diǎn)和失去焦點(diǎn)的時(shí)候均會(huì)被調(diào)用,對(duì)應(yīng)Activity的onResume和onPause方法。
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
if (hasFocus)
{
System.out.println("onWindowFocusChanged width=" + mTextView.getWidth() + " height=" + mTextView.getHeight());
}
}
- 2 View.post(runnable).通過post可以將一個(gè)runnable投遞到消息隊(duì)列的尾部,然后等待Looper調(diào)用此runnable的時(shí)候,View已經(jīng)初始化好了。
view.post(new Runnable(){
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
- 3 ViewTreeObserver. 使用ViewTreeObserver的很多回調(diào)方法可以獲取View的寬高,比如OnGlobalLayoutListener和OnPreDrawListener。當(dāng)View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View可見性發(fā)生改變時(shí),OnGlobalLayoutListener會(huì)被回調(diào),伴隨著View樹的狀態(tài)改變等,addOnGlobalLayoutListener方法會(huì)被回調(diào)多次。
// MineFragment中,顯示"圈子,新人必讀"引導(dǎo)頁(yè)的PopupWindow中使用
private void showPopWindow() {
layoutAbovePopWindow.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
changeWindowAlpha(0.7f);
popupWindow.showAsDropDown(layoutAbovePopWindow, 0, Tools.getPixelByDip(getActivity(), 10));
popupWindow.update();
layoutAbovePopWindow.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
// 開戶上傳身份證照片UploadPhotoActivity.java中使用
private void initUI() {
getPresenter().initUploadLayout(mCurrentPartnerId);
mUploadPhotoView.setClearListener(this);
mUploadPhotoView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
int photoLayoutHeight = mUploadPhotoView.getMeasuredHeight();
if (photoLayoutHeight > 0) {
mUploadPhotoView.setPhotoLayout(photoLayoutHeight);
}
mUploadPhotoView.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
- 4 手動(dòng)對(duì)View進(jìn)行measure來得到View的寬高,該方法不常用。