前言
上一篇文章深入理解MeasureSpec我們分析了View的測量規(guī)格,根據(jù)MeasureSpec才能測量出View的大小,下面我們詳細分析一下View的測量過程-measure過程,measure過程主要是測量出View的寬/高,其實View的測量分為兩種情況,一是直接繼承View的,通過OnMeasure()
過程測量自己就可以了,還有一種是繼承自ViewGroup的,除了測量自身外,還要遍歷去測量所有子View,子View在依次執(zhí)行測量過程。
View的measure過程
View的measure過程是從ViewRootImpl的performMeasure()
開始的,performMeasure()
里面會調用View的measure()
方法,而View的measure()
是用final
修飾的,意味著子類不能重寫該方法,但是在View的measure()
方法里面會去調用我們熟悉的onMeasure()
方法,也就是開始了View的測量過程,下面我們看下View的onMeasure()
方法
View#onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
我們發(fā)現(xiàn)這里面邏輯很簡潔,就是調用setMeasuredDimension()
方法,也就說當我們自定義一個View重寫onMeasure()
時,必須調用setMeasuredDimension()
方法,這個方法會存儲View的測量值,我們看下參數(shù)里的getDefaultSize()
方法
View#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;
}
這里邏輯也很好理解,我們只需要看MeasureSpec.AT_MOST和MeasureSpec.EXACTLY這兩種情況,getDefaultSize()
返回的大小就是測量后的大小,對于MeasureSpec.UNSPECIFIED這種模式,一般用于系統(tǒng)內部的測量過程,View的大小是系統(tǒng)的建議值,也就是getDefaultSize()
的第一個參數(shù)getSuggestedMinimumHeight()
的返回值,寬高同理
View#getSuggestedMinimumWidth()
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
我們發(fā)現(xiàn)就是根據(jù)View是否設置背景進行判斷的,如果沒設置背景就返回mMinWidth
,設置背景看就返回mMinWidth
和背景寬度的最大值,mMinWidth
對應于android:minWidth
,如果不指定默認為0;我們再看下mBackground.getMinimumWidth()
的含義,在View的全局變量里我們看到private Drawable mBackground;
背景就是一個Drawable,再看下getMinimumWidth()
Drawable#getMinimumWidth()
* @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;
}
我們看到注釋沒如果沒設置背景就返回0,否則就返回背景的原始寬度,這個原始寬度又是什么呢,例如在xml中定義shape填充這種純色,原始寬度就為0,如果設置BitmapDrawable為背景,原始寬度就不為0.通過以上可以看出getSuggestedMinimumWidth()
返回的就是View在MeasureSpec.UNSPECIFIED這種模式下測量的大小。以上就是我們分析普通View的onMeasure過程。
ViewGroup的measure過程
ViewGroup是繼承與View的,它是一個抽象類沒有重寫onMeasure方法,但是有一個measureChildren()
方法,通過名字我們也能猜到就是測量子View的方法,下面我們詳細看下
ViewGroup#measureChildren()
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);
}
}
}
這里就是通過遍歷所有的子View并且不是GONE的情況下去執(zhí)行measureChild()
方法,這也很好的解釋了為什么我在將某個View設置GONE后,不占用空間的原因,因為根本就沒有去測量,我們再看下measureChild()
方法
ViewGroup#measureChild()
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);
}
這里邏輯也很簡單,首先取出子View的LayoutParams,將父控件的MeasureSpec和LayoutParams作為參數(shù)傳到getChildMeasureSpec()
方法中來創(chuàng)建子View的MeasureSpec,getChildMeasureSpec()
這個方法在上一篇深入理解MeasureSpec中已經(jīng)詳細的介紹,請自行查看!子View調用 measure
方法,里面會執(zhí)行View的onMeasure()
方法,下面我們看下FrameLayout的測量過程。
FrameLayout#onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//判斷當前布局的寬高是否為EXACTLY模式
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) {
//對每一個子View進行測量
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非EXACTLY模式,添加到mMatchParentChildren中
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));
//子View中設置為match_parent的個數(shù)
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();
//對FrameLayout的寬度規(guī)格設置,因為這會影響子View的測量
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);
}
...
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
首先,F(xiàn)rameLayout根據(jù)它的MeasureSpec來對每一個不為GONE的子View進行測量,即調用measureChildWithMargin()
方法,measureChildWithMargin()
這個方法和measureChild()
類似,多計算了邊距,再對每一個子View進行測量,之后會尋找其中最大的寬高,那么FrameLayout的測量寬高會受到這個子View的最大寬高的影響(wrap_content模式),接著調用setMeasureDimension方法,把FrameLayout的測量寬高保存。最后則是特殊情況的處理,即當FrameLayout為wrap_content屬性時,如果其子View是match_parent屬性的話,則要重新設置FrameLayout的測量規(guī)格,然后重新對該部分View測量。
總結
上面我們已經(jīng)分析了View的Measure過程,測量控件大小是父控件發(fā)起的
父控件要測量子控件大小,需要重寫onMeasure()
方法,然后調用measureChildren()
或者measureChildWithMargins()
方法
測量控件的步驟:
父控件onMeasure
->measureChildren/measureChildWithMargin
->getChildMeasureSpec
->
子控件的measure
->onMeasure
->setMeasureDimension
->
父控件onMeasure
結束調用setMeasureDimension
最后保存自己的大小.