前言
一 View的測量
首先回到ViewRootImpl#performTraversals函數:
private void performTraversals() {
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 測量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//................
}
}
} else {
//................
}
//................
}
if (didLayout) {
//定位
performLayout(lp, mWidth, mHeight);
//................
}
//................
//繪製
performDraw();
//................
}
繼續查看performMeasure函數:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//mView就是DecorView,從頂級View開始了測量流程
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
說明,在這裏我需要看下childWidthMeasureSpe,childHeightMeasureSpec形參是怎麼來的,如下:
//獲取寬高的測量規格
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
那麼getRootMeasureSpec是??
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// 如果View的高寬屬性爲match_parent那麼它的mode將會是EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 如果View的高寬屬性爲 wrap_conent 那麼它的mode將會是 AT_MOST
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 如果View的高寬屬性爲具體數值那麼它的mode將會是 EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
說明,在這裏我門看到了一個對象MeasureSpec,MeasureSpec的作用是在在Measure流程中,系統將View的LayoutParams根據父容器所施加的規則轉換成對應的MeasureSpec(規格),然後在onMeasure中根據這個MeasureSpec來確定view的測量寬高。同時可以從函數可以看出:
(1).如果View的屬性爲match_parent|確定值,那麼它對應的mode爲exactly;
(2).如果View的屬性爲wrap_parent,那麼它對應的mode爲at_most;
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
/**
* UNSPECIFIED 模式:
* 父View不對子View有任何限制,子View需要多大就多大。
* 其值左位移30位:00 000000000000000000000000000000(32個0,前兩位表示mode,後30位表示size)*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* EXACTYLY 模式:
* 父View已經測量出子Viwe所需要的精確大小,這時候View的最終大小
* 就是SpecSize所指定的值,對應於match_parent和精確數值這兩種模式。
* 其值左位移30位:01 000000000000000000000000000000(32個0,前兩位表示mode,後30位表示size)
* */
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* AT_MOST 模式:
* View的大小不能超過父View給定的specSize的大小,對應wrap_content這種模式。
* 其值左位移30位:10 000000000000000000000000000000(32個0,前兩位表示mode,後30位表示size)
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* 包裝一個測量規格
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on 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);
}
}
/**
* 生成一個安全的測量規格
* Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
* will automatically get a size of 0. Older apps expect this.
*
* @hide internal use only for compatibility with system widgets and older apps
*/
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
/**
* 從規格中解析出mode,其實就是得到32位中的前兩位數值
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
/**
* 從規格中解析出size,其實就是得到32位中的後30位數值
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
int size = getSize(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return makeMeasureSpec(size, UNSPECIFIED);
}
size += delta;
if (size < 0) {
Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
") spec: " + toString(measureSpec) + " delta: " + delta);
size = 0;
}
return makeMeasureSpec(size, mode);
}
}
說明,那麼在此處我們可以看到, 在出我們測量方法當中他傳遞的並不是一個純粹的size,而是遵循了我們設置寬高時的幾種模式:EXACTLY ,ATMOST ,UNSPECIFIED這三個模式的本質是0,1,2的左位移30位,那麼其實我們先能理解爲我們實際上在傳遞值得過程當中將顯示模式mode+size打包一起交給measure方法,而裏面的數據結構其實實際上是一個32位的數值,我們可以明顯看到幾個模式的值在後面進行了左位移操作了30位,用MODE_SHIFT 操作之後,實際表明一個32位的值30,前兩位作爲MODE,而MODE_MASK 表示是後30位,後30位爲size。
具體如何運算的查看《MeasureSpec詳解》。
繼續查看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);
}
//........
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) {
// 核心就在這裏,調用了onMeasure方法,不管是LinearLayout或者是FreamLayout還是其他佈局,
// 他們都是通過測量組件,實現我們的佈局定位,每一個Layout的onMeasure實現都不一樣.
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;
}
//........
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
//........
}
接下來,我們以FramLayout爲例,查看onMeasure方法:
/**
* 該方法需要自己實現,在這個方法處理測量子view的業務
* @param widthMeasureSpec 父View 寬度規格
* @param heightMeasureSpec 父View 高度規格
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//獲取當前佈局內的子View數量
int count = getChildCount();
//判斷當前佈局的寬高是否是match_parent模式或者指定一個精確的大小,如果是則置measureMatchParent爲false.
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
//mMatchParentChildren 爲View集合,當FrameLayout的高寬屬性爲wrap_content,用來記錄高寬屬性爲match_content的子View。
mMatchParentChildren.clear();
//記錄最大高度
int maxHeight = 0;
//記錄最大寬度
int maxWidth = 0;
int childState = 0;
//遍歷所有類型不爲GONE的子View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//TODO 1.對每一個子View進行測量,最終還是會調用measure方法
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//尋找子View中寬高的最大者,因爲如果FrameLayout是wrap_content屬性
//那麼它的大小取決於子View中的最大者
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());
//如果FrameLayout是wrap_content模式,那麼往mMatchParentChildren中添加
//寬或者高爲match_parent的子View,因爲該子View的最終測量大小會受到FrameLayout的最終測量大小影響
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
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());
}
//TODO 2.保存測量結果,其中調用了resolveSizeAndState函數
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
//只有FrameLayout的模式爲wrap_content的時候纔會執行下列語句
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//子View的寬度測量規格
final int childWidthMeasureSpec;
/**
* 如果子View的寬度是match_parent屬性,那麼view的可用寬度將會是FrameLayout的測量寬度
* 減去padding和margin後剩下的空間。
*/
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 {
/* TODO 3.調用 getChildMeasureSpec函數獲取子view的規格
* 參數一:zi view的測量規格
* 參數二:間距
* 參數三:view的高寬
*/
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);
}
//對於這部分的子View需要重新進行measure過程
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
先看標註1函數measureChildWithMargins:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//此時widthUsed =0 已經被使用的寬度空間,heightUsed =0 已經被使用的高度空間,
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//獲取子View的寬度規格
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
//獲取子View的高度規格
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//調用子View的Measure函數
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
繼續查看getChildMeasureSpec函數:
/**
* @param spec 父view測量規格
* @param padding 已經被使用的空間
* @param childDimension 子View的大小
* @return
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父view的mode
int specMode = MeasureSpec.getMode(spec);
//父view的size
int specSize = MeasureSpec.getSize(spec);
//可供View使用的剩餘空間,
// 無論父view的mode是exactly或at_most或unspecified,view的大小都不可能超過size
int size = Math.max(0, specSize - padding);
// view最終可以使用的空間,但不一定就是view的最終size,如果它有子view,可能會受到子view的影響,進行調整。
int resultSize = 0;
// view的mode
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
//如果父Viewmode爲EXACTLY,View的高寬爲確定值那麼其mode爲exactly,resultSize 爲childDimension
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果父Viewmode爲EXACTLY,View的高寬爲MATCH_PARENT,那麼其mode爲exactly,resultSize 爲size
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.
//如果父Viewmode爲EXACTLY,View的高寬爲 WRAP_CONTENT 那麼其mode爲 AT_MOST,resultSize 爲size
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
//如果父Viewmode爲 AT_MOST,View的高寬爲確定值那麼其mode爲exactly,resultSize 爲childDimension
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
//如果父Viewmode爲 UNSPECIFIED,View的高寬爲確定值那麼其mode爲exactly,resultSize 爲childDimension
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);
}
說明,從函數可以看出:(1).不管父View的mode爲exactly或at_most或unspecifited,那麼子View的size都不可能大於剩餘空間size的大小;
(2).當子View的高寬爲確定值時,那麼它的mode必爲exactly。
回到onMeasure函數中查看2標註的函數setMeasureDimension:
再setMeasureDimension之前先看resolveSizeAndState:
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
//view的mode
final int specMode = MeasureSpec.getMode(measureSpec);
//view的size
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST://此種情況下取最小值
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY://此種情況下取 specSize
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
說明,該函數處理的結果將會作爲父View的高寬進行保存。
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 函數把高寬保存到成員變量mMeasuredWidth、mMeasuredHeight中。
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
繼續查看setMeasuerDimensionRaw函數:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
this.mMeasuredWidth = measuredWidth;
this.mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
說明,可以看出,父view的高寬保存到了成員變量mMeasuredWidth 、mMeasuredHeight,當然我們可以通過getMeasureXXX獲取其值,如下:
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
/**
* Return the full width measurement information for this view as computed
* by the most recent call to {@link #measure(int, int)}. This result is a bit mask
* as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
* {@link #getWidth()} to see how wide a view is after layout.
*
* @return The measured width of this view as a bit mask.
*/
@ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
@ViewDebug.FlagToString(mask = MEASURED_STATE_MASK, equals = MEASURED_STATE_TOO_SMALL,
name = "MEASURED_STATE_TOO_SMALL"),
})
public final int getMeasuredWidthAndState() {
return mMeasuredWidth;
}
/**
* Like {@link #getMeasuredHeightAndState()}, but only returns the
* raw height component (that is the result is masked by
* {@link #MEASURED_SIZE_MASK}).
*
* @return The raw measured height of this view.
*/
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
/**
* Return the full height measurement information for this view as computed
* by the most recent call to {@link #measure(int, int)}. This result is a bit mask
* as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
* {@link #getHeight()} to see how wide a view is after layout.
*
* @return The measured height of this view as a bit mask.
*/
@ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
@ViewDebug.FlagToString(mask = MEASURED_STATE_MASK, equals = MEASURED_STATE_TOO_SMALL,
name = "MEASURED_STATE_TOO_SMALL"),
})
public final int getMeasuredHeightAndState() {
return mMeasuredHeight;
}
小結:
從上述我們可以總結到,FrameLayout根據它的MeasureSpec來對每一個子View進行測量,即調用measureChildWithMargin方法,對於每一個測量完成的子View,會尋找其中最大的寬高,那麼FrameLayout(wrap_content模式)的測量寬高會受到這個子View的最大寬高的影響,接着調用setMeasureDimension方法,把FrameLayout的測量寬高保存。最後則是特殊情況的處理,即當FrameLayout爲wrap_content屬性時,如果其子View是match_parent屬性的話,則要重新設置FrameLayout的測量規格,然後重新對該部分View測量。