接著上一篇View繪制流程及源碼解析(一)——performTraversals()源碼分析,這一篇我們來具體看看三大流程的實現過程。
一. 從MeasureSpec說起
由于劇情的需要,我們不得不先說一下這個MeasureSpec。為什么要先說這個東西呢?再上一篇文章中,我們是否還記得三大流程正式開始的地方的代碼:
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
可見,getRootMeasureSpec()是獲取根布局的MeasureSpec,而最終的performMeasure()正式執(zhí)行measure()的流程就是所傳遞就去的參數就是這個getRootMeasureSpec()所計算得到的值,因此我們先要搞清楚這個MeasureSpec是什么玩意。
??那么什么是MeasureSpec呢?首先,他代表的是一個32位的int值。其次,它是View類中的一個內部類,所以我們再接下來的文章分析中,可能會用到它所代表的int值和它本身這兩種情況,希望讀者再閱讀的時候可以加以區(qū)分。我們來看看它的源碼中的注釋(framewoks/base/core/java/android/view/View):
/**
* 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. There are three possible
* modes:
* <dl>
* <dt>UNSPECIFIED</dt>
* <dd>
* The parent has not imposed any constraint on the child. It can be whatever size
* it wants.
* </dd>
*
* <dt>EXACTLY</dt>
* <dd>
* 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.
* </dd>
*
* <dt>AT_MOST</dt>
* <dd>
* The child can be as large as it wants up to the specified size.
* </dd>
* </dl>
*
* MeasureSpecs are implemented as ints to reduce object allocation. This class
* is provided to pack and unpack the <size, mode> tuple into the int.
*/
public static class MeasureSpec {
上面的注釋交代了四點:
(1)MeasureSpec封裝了從父布局傳遞給子布局的的布局要求。
(2)每一個MeasureSpec代表了一個對寬度或著高度的要求。
(3)一個MeasureSpec由一個size和一個mode組成,有三種可能:
??①UNSPECIFIED:父布局對于子布局/Veiw沒有任何約束,子布局可以是任何它想要的尺寸。根據主席的說法,這中情況一般用于系統(tǒng)內部反復測量控件大小,我們再用的過程中很少見到。
??②EXACTLY:
??父布局精確設定了子布局/View的大小,無論子布局想要多大的都將得到父布局給予的精確邊界。這種情況實際上就對應的是我們在xml文件或者LayoutParams中設置的xxdp/px或者MATH_PARENT這兩種情況,也就是精確的給予了布局邊界。
??③AT_MOST:在確定的尺寸內,子布局/View可以盡可能的大。這個確定的尺寸,當然指就值得就是父布局的大小,畢竟子布局/View再大,也不能超出父布局的大小。這種情況對應的就是我們再xml文件或者LayoutParams中設置的WRAP_CONTENT,子View可以隨著內容的增加而申請更大的尺寸,但是不能超過父布局的大小。
(4)MeasureSpec將SpecMode和SpecSize打包成一個int值以便減少對象分配(的開支)。這個類提供了打包和解包size,mode的各中方法。
前面我們提到過,MeasureSpec是一個32位的int值,通過上面一段我們又知道他是一個大小(Size)和模式(Mode)的組合值,它的存在是為了減少對象分配的開支,那么我們可以做出進一步解釋:
??①32位的int值中,高2位是SpecMode,代表測量模式;低30位是SpecMode,代表再某種模式下得出的測量大小。
??②我們在上面(1)(2)中我們將"要求"兩個字著重加黑,就是為了強調:這個MeasureSpec的值是"一個父傳遞給子測量要求","怎么測量的要求",不是父對子強加的布局參數什么的,至于這個要求是什么,怎么要求的,后面我們會做進一步說明。
??③至于為什么要強調"傳遞"兩個字,主要是因為,上面的"測量要求"不是父強加給子的,而是兩個人一起商量著來的。具體的說,對于除了DecorView外的普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的,當然從源碼來看還與View的margin和padding有關;而對于DecorView來說,其MeasureSpec由它自身的LayoutParams決定(畢竟它已經是根布局了,上面再沒人了)。
說了這么一大通,下面我們從源碼的角度帶大家分析一下根布局和自布局確定MeasureSpec的過程。
(一).根View/DecorView的MeasureSpec
前面開篇代碼中的getRootMeasureSpec()看名字我們也知道他是獲得根布局的MeasureSpec的方法
(framewoks/base/core/java/android/view/ViewRootImpl):
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;
}
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
通過上述代碼,我們可以看到RootView(DecorView)的MeasureSpec創(chuàng)建的全過程,分為三種情況:
- LayoutParams.MATH_PARENT:精確模式,強制讓RootView的大小等于Window的大小。
- LayoutParams.WRAP_CONTENT:最大模式,限制了RootView的最大窗口(父布局/View的大小。
- DEFAULT:指定大小,也就是在xml或者LayoutParams中指定View的寬度和高度的px/dp數。
在這三種情況下,分別調用makeMeasureSpec()方法,將模式和大小值打包成一個int值返回。
(二).普通View的MeasureSpec
獲取普通View(除DecorView以外的View,大多數情況下指的是子布局/View)的MeasureSpec值的代碼在ViewGroup類中,相對復雜一點(framewoks/base/core/java/android/view/ViewGroup):
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);
//通過父View的MeasureSpec和子View的自己LayoutParams的計算,算出子View的MeasureSpec,然后父容器傳遞給
//子容器的然后讓子View用這個MeasureSpec(一個測量要求,比如不能超過多大)去測量自己,如果子View是
//ViewGroup 那還會遞歸往下測量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,和開篇代碼中root測量的代碼一樣,這里的Child在測量之前,先得getChildMeasureSpec()。還記得我們上面說的嗎?對于除了DecorView外的普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的,當然從源碼來看還與View的margin和padding有關,這里我們可以看源碼感受一下:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
//這里的mWidth就是DecorView的寬度,lp是它的LayoutParams參數。
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
可以看到,getRootMeasureSpec中,只需要傳遞進去DecorView自身的寬度和它的LayoutParams寬度設置這兩個參數;而getChildMeasureSpec中則需要考慮父布局的MeasureSpec(parentWidthMeasureSpec),父View的Padding,自身設置的Margin,已經用掉的空間大小(widthUsed),LayoutParams的寬度設置(lp.width)。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //父View的mode
int specSize = MeasureSpec.getSize(spec); //父View的size
//(父View的大小-自己的Padding+子View的Margin),得到值才是子View的大小,所以這里的size并不是子View的大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
//父View強加了一個精確的值給我們,父布局的模式是EXACTLY。這種情況對應于MATH_PARENT和確定的px/dp值這兩種情況。
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//子View的寬度和高度值為一個確定的dp/px值。
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//子布局的LayoutParams為MATCH_PARENT,那么最終得出的模式為EXACTLY。
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.
//子布局的LayoutParams設為WRAP_CONTENT,那么最終的布局模式為AT_MOST。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
//父布局強加給我們一個最大值,即父布局的模式為AT_MOST.
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
//這個時候如果子布局/View想要一個精確的尺寸(xxdp/px),那么最終的結果為EXACTLY。
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.
//如果子View的LayoutParams為MATH_PARENT,那么最終得到的結果為AT_MOST。
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.
//如果子View的LayoutParams為WRAP_CONTENT,那么最終的結果模式為AT_MOST。
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;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
我們將上面的這段代碼中子View的模式和父View模式的對應關系用一個表格表示一下:
這里的resultMode為最終得到的子VIew的SpecMode,resultSize為最終得到的子View的大小。而childeSize就是上面的childDimension,即精確對定義子View大小的時候我們在LayotParams中所設定的子View大小,parentSize則為父容器中目前剩余的可用大小。
二.measure()過程
(一).View的measure()過程
好了,清楚了MeasureSpec這個值以及我們如何得到父View及View的MeasureSpec之后呢,我們接下來就開始看三大流程的第一步:Measure(測量)過程(framewoks/base/core/java/android/view/View):
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if (cacheIndex < 0 || sIgnoreMeasureCache) {
......
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
可以看到,這個Measure()方法是final的,也就是說我們不能重寫,但是由于具體的測量過程是在onMeasure()中完成的,所以有需要的話我們可以重寫這個方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
......
顧名思義,這個setMeasuredDimension()方法是設置View寬/高的測量值,而它傳入的兩個參數getDefaultSize方法則是獲取到的寬/高值。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
首先我們來看看它的第一個參數getSuggestedMinimumWidth(),只有一句代碼,比較簡單,他返回的是View的建議寬度/高度。這個建議值是由View的背景尺寸和它的mMinWidth屬性決定的,mBackground就是背景,mMinWidth表示View的xml文件中android:minWidth屬性所指定的值,如果這個屬性沒有設置,那么就默認為0。
??從源碼來看,這個方法中通過一個三目運算符,顯示判斷mBackground是否為null,也就是有沒有。如果為ture也就是沒有背景,那么就返回mMinWidth的值,這個值可以為0;如果View指定了背景,那么返回的是mMinWidth和mBackground最小寬度兩個值中較大的那個。這里指貼了getSuggestedMinimumWidth的源碼,getSuggestedMinimumHeight()的源碼是一樣的。
??現在我們清楚了getDefaultSize()方法中第一個參數的意義,第二個參數measureSpec就是上面我們一直講的寬度/高度的MeasureSpec值?,F在我們來看看這個方法:
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這種情況,返回值是上面getSuggestedMinimumWidth()得到的值。對于我們來說,只需要關注AT_MOST和EXACTLY這兩種情況就行了。在這兩種情況中,返回值也就是View的測量值都等于 MeasureSpec.getSize(measureSpec):
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
這個方法中做了什么呢?我們已經知道,這個measureSpec是一個封裝了View的SpecMode和SpecSize大小的int值,其中高兩位是模式,低三十位是大小。而這個MeasureSpec.getMode(measureSpec);
和MeasureSpec.getSize(measureSpec);
實際上就是分別將傳遞進去的measureSpec中封裝的SpecMode和SpecSize剝離出來。
??對于DecorView/View,measureSpec就是我們在開篇最上面的代碼中計算出來的getRootMeasureSpec(mWidth, lp.width)。所以這里的specSize(對于DecorView)也就是我們最上邊代碼中的mWidth,也就是DecorView的Window的寬度。
??而對于其他的一些View的派生類,如TextView、Button、ImageView等,它們的onMeasure方法系統(tǒng)了都做了重寫,不會這么簡單直接拿 MeasureSpec 的size來當大小,而去會先去測量字符或者圖片的高度等,然后拿到View本身content這個高度(字符高度等),如果MeasureSpec是AT_MOST,而且View本身content的高度不超出MeasureSpec的size,那么可以直接用View本身content的高度(字符高度等),而不是像View.java 直接用MeasureSpec的size做為View的大小。
??還有一點需要說明的是,上面我們強調View測量值的原因是,View最終值是在Layout階段確定的,雖然幾乎是所有情況下View測量大小和最終大小是一樣的。
(二).ViewGroup的measure()過程
對于ViewGroup來說,出了完成自己的測量過程外,還會去遍歷調用所有子View/ViewGroup的measure()方法,各個子元素再去遞歸執(zhí)行這個方法。和View不同的是,ViewGroup是一個抽象類,他并沒有重寫真正實現測量自身的onMeasure()方法,而是將該方法下發(fā)到了各個子類去實現。為什么ViewGroup不像View一樣對其onMeasure()方法做同意的實現呢?主要是因為不同的ViewGroup子類有不同的布局特性,比如LinearLayout和RelativeLayout這兩者的布局特性顯然不同,因此ViewGroup無法做統(tǒng)一的實現。
??雖然ViewGroup測量自身的過程下放到了各個子類中去實現,但是在該類中我們任然可以看到該類遞歸調用子類測量方法的邏輯:
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);
}
}
}
遍歷子Veiw,只要不是GONE的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);
}
measureChild()方法中做的事情就是,取出子View的LayoutParams值,然后通過getChildMeasureSpec()方法(這個方法上面講過)得出子元素的MeasureSpec,然后直接調用子View的measure(),之后所做的事情就和"(一).View的measure()過程"中的過程一樣了。
??上面就是子View的測量邏輯,下面我們需要著重看一下ViewGroup()自身的測量過程,這里我們選擇LinearLayout來做分析(待會實例分析會用到)(framewroks/base/core/java/android/widget/linearlayout):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
我們只分析豎直方向的測量過程,水平方向的過程是一樣的:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0; //所有子View高度和
int maxWidth = 0; //所有子View中最大寬度值
......
final int count = getVirtualChildCount(); //豎直方向的子View總數
final int widthMode = MeasureSpec.getMode(widthMeasureSpec); //獲取父布局的寬/高的模式
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false; //是否跳過測量
......
int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.獲取每一個子View的高度,同時記錄最大的寬度
for (int i = 0; i < count; ++i) { //循環(huán)遍歷每一個子View
final View child = getVirtualChildAt(i); //一個一個的獲取子View
......
if (child.getVisibility() == View.GONE) { //如果這個子View的可見性為GONE
i += getChildrenSkipCount(child, i); //將這個子View連同他的序號一并計入getChildrenSkipCount()這個方法中
continue;
}
if (hasDividerBeforeChildAt(i)) { //如果這個子View帶有divider分割線
mTotalLength += mDividerHeight; //mTotalLength累加分割線的高度。
}//mDividerHeight對應android:divider="@drawable/spacer_medium"這個屬性的高度,通常添加的是一個drawable圖片
//獲取子View的LayoutParams屬性
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight; //totalWeight累加LayoutParams中的weight屬性值
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
//如果這個子View高度的heightMode為EXACTLY,并且高度為0,并且比重大于0。對照上文的表格可知,當父mode為EXACTLY
//時,此時子View的高度為一個確定的值(這里為0),那么最終的高度就為childSize也就是0.顯然這個時候沒有可見的View。
final int totalLength = mTotalLength;
//注意這里的totalLength/mTotalLength表示的是之前測量過的子View的高度和(以及如果本次測量的View有divider分
//割線的話就加上本次測量View的分割線圖片高度)
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
//此時總高度(本次+測過的)就是totalLength和(totalLength+本次測量View的上下Margin)兩個值中較大的那個.
skippedMeasure = true; //跳過測量的標志位(畢竟這個高度是0,也就是沒有可見的View,就沒有測量的意義了)
} else {
int oldHeight = Integer.MIN_VALUE; //oldHeight設為負無窮
if (lp.height == 0 && lp.weight > 0) {
//else代表著此時父View的模式heightMode為UNSPECIFIED或者AT_MOST,并且這個子View想延伸以填充剩余的可用
//空間(雖然它的高度為0,但是它的比重不為0,說明他有填充剩余可用空間的“欲望”),此時我們將它的模式轉變成
//WRAP_CONTENT以便它最終的高度不為0(同樣對照上文的表格克制此時子View的大小為父View剩余空間的大小,相當于
//match_parent,當然這里暫時不考慮UNSPECIFIED這種情況。)
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
//最終決定子View大小的方法
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec, ①
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {//如果oldHeight不為負無窮,說明經歷了上段代碼中else的情況
lp.height = oldHeight; //此時height=oldHeight=0
}
final int childHeight = child.getMeasuredHeight(); //獲取當前子View的測量高度
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
//mTotalLength + childHeight +lp.topMargin +lp.bottomMargin + getNextLocationOffset(child)
//表示之前測量過的子View的總高度+本次測量的子View的高度+本次測量的子Veiw的上下Margin,最后一個方法返
//回值為0,可以直接忽略。
//那么此時總高度(之前的子View+本次測量的子View)就等于之前測量的子View的總高度和上面一串串值,兩者中的
//較大者,之所以這么干是因為margin+height的值可能是負的,不能因為margin的影響就縮小了總高度的測量和,
//這樣做顯然得出的不是真實的高度.
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
//這里的largestChildHeight表示高度最大的子Veiw
}
}
......
final int margin = lp.leftMargin + lp.rightMargin; //左右Margin
final int measuredWidth = child.getMeasuredWidth() + margin; //本次測量的子View的寬度+左右Magin
maxWidth = Math.max(maxWidth, measuredWidth); //獲取總的子View中最大的Veiw寬度
......
}
上面這段代碼就是循環(huán)遍歷測量LinearLayout中所有子View的代碼,主要獲取兩個值:每個子View的高度和所有子View中最大的寬度。其中高度和寬度均是加上上下/左右Margin之后的結果。注釋已經寫的很清楚了,這里不在鰲述。
??當LinearLayout測量完了所有子View之后,就需要開始測量自身了:
......
mTotalLength += mPaddingTop + mPaddingBottom; //上面的for循環(huán)已經計算完了所有的子Veiw的高度,這里開始計算
//LinearLayout自己的高度,也就是在所有子View高度之和的基礎之上加上上下Padding
int heightSize = mTotalLength;
// 將高度值再做一遍比較,即之前的高度值和getSuggestedMinimumHeight()較大的那個,getSuggestedMinimumHeight()前面講過
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); ②
heightSize = heightSizeAndState & MEASURED_SIZE_MASK; //最終得出的LinearLayout總高度
......(省略針對不同情況縮放子View的代碼)
maxWidth += mPaddingLeft + mPaddingRight; //LinearLayout總寬度
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); //LinearLayout總寬度
//所有的子View測量之后,經過一系類的計算之后通過setMeasuredDimension()設置自己的寬高,對于LinearLayout,可能是高度的
//累加,對于FrameLayout 可能用最大的字View的大小,
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
當子元素測量完之后,LinearLayout就會根據子元素的測量情況來測量自己的大小。針對豎直方向的LinearLayout,它的寬度測量實際上就是View的測量過程(最大的子View寬度+左右margin+左右padding),具體到代碼中為:
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
maxWidth += mPaddingLeft + mPaddingRight;
而LinearLayout的高度測量稍微復雜一點,具體來說是指,如果它的布局中高度采用的是math_parent或者具體的數值,那么它的測量過程和View一致,即高度為sizeSpec;如果它的布局中高度采用的是wrap_content,那么它的高度是所有子元素占用的高度總和,但是仍然不能超過它父容器的剩余空間,當然它的最終高度還是要考慮再豎直方向得到padding.(這段結論取自《Android開發(fā)藝術探索》,筆者在分析中并沒有找到支持該結論的相關源碼,但是相信主席一回,如果有人找到了歡迎指教)
??最終決定它的高度的是上面②處的resolveSizeAndState()方法(framewoks/base/core/java/android/view/View):
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
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:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
這個方法主要的作用是在父布局的MeasureSpec約束下,協(xié)調所需要的大小和狀態(tài)。其中size是當前LinearLayout所需要的寬度(最大寬度),measureSpec是父布局的MeasureSpec,childMeasuredState為子View的測量狀態(tài)。上面的代碼已經很清楚了,這里不再加以分析。
這里還要說明的一點就是上段代碼中①處的measureChildBeforeLayout()方法:
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
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);
}
可以看到,改方法最終調用了ViewGroup類中的measureChildWithMargins()方法,最終調用了View類中的measure()方法。
(三)舉例說明
進過上面的過程我們的measure過程的源碼就分析完了,下面我們通過一個例子來具體說明下這個過程。首先呢,我們要回顧一下淺談Activity從建立到顯示(setContentView源碼淺析)中的一些內容:這片文章中,我們
為了說明開發(fā)中我們自己寫的xml文件在DecorView的層次關系,用到了一個系統(tǒng)定義的xml文件:R.layout.screen_simple,這個布局是我們的theme設置為NoTitleBar時的布局(SDK/platforms/android-23/data/res/layout/screen_simple.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
這個時候我們定義了一個activity_main.xml文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:paddingBottom="50dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hellow word"
android:textSize="20sp"/>
<View
android:id="@+id/selfView"
android:layout_width="match_parent"
android:layout_height="100dp" />
</LinearLayout>
并且畫出了兩者的層級關系圖:
OK,相信通過上一篇文章的分析你已經清楚上面這張途中各部分之間的關系了,下面我們對上面布局中的一些元素做一下必要的說明:
??①上面我們說過,這個R.layout.screen_simple是我們將系統(tǒng)的theme設置為NoTitleBar時的布局。NoTitleBar也就意味著沒有titlebar或者actionbar的干擾,整個content區(qū)域只有我們的activity_main.xml,這樣就方便了我們之后對于測量流程的分析。
??②圖中id為statusBarBackgroud的Veiw,顧名思義就是狀態(tài)欄背景。為了抓住主要矛盾,在之后的分析中我們會跳過狀態(tài)欄的測量分析。
??③screen_simple下屬的ViewStub,我們對這個東西做一下說明:ViewStub是View的子類;它不可見(可見性為GONE),寬高均為0;它用來延遲加載布局資源(惰性加載),常用于布局優(yōu)化。顯然,ViewStub在這里是用來惰性加載titlebar/actionbar的,關于什么是惰性加載,讀者可以自行搜索;另外,由于它的可見性為GONE,并且寬高都是0,因此再measure()的過程中系統(tǒng)也會自動跳過。
OK,下面我們正式開始測量流程的分析。首先,我們要回到三大流程開始的地方,也就是ViewRootImpl類中的performTraversals()方法,不清楚的同學可以看View繪制流程及源碼解析(一)——performTraversals()源碼分析這片文章。再這片文章的"第六段代碼"中,我們看到了measure()過程的開始方法——performMeasure():
......
//mStopped的注釋:Set to true if the owner of this window is in the stopped state.如果此窗口的所有者
//(Activity)處于停止狀態(tài),則為ture.
if (!mStopped || mReportNextDraw) { //mReportNextDraw Window上報下一次繪制.
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
//mWidth != host.getMeasuredWidth() 表示frame的寬不等于初始DecorView寬.
//getMeasuredWidth()方法可以獲取View測量后的寬高,host上面說過為DecorView根布局.
//獲得view寬高的測量規(guī)格,lp.width和lp.height表示DecorView根布局寬和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//開始執(zhí)行測量操作 ①
第一步,DecorVeiw的測量
??通過上篇文章之前的分析可知,這里的getRootMeasureSpec()就是獲取DecorView的MeasureSpec,也就是說這里進行的是頂層View——DecorView的測量。在performMeasure()方法中,我們調用了DecorView的measure()方法:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //mView就是DecorView
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
而DecorView是一個FrameLayout,因此我們需要區(qū)看FrameLayout類的onMeaure()方法,這個方法相對LinearLayout的簡單多了(早知道在上面我就直接分析FrameLayout了,mdzz):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
.......
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) {
// 循環(huán)遍歷的子View,只要不是GONE的都會參與測量
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
}
}
......
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
......
}
需要說明的一點是,由于DecorView是最頂層的View,因此它的寬高MeasureSpec是由它自己的寬度和LayoutParams決定的getRootMeasureSpec(mWidth, lp.width);
和getRootMeasureSpec(mHeight, lp.height);
。再者,由于DecorView是最頂層View的緣故,通常情況下它可以代表整個屏幕,因此mWidth和mHeight既是DecorView的寬高也是整個屏幕的寬高。lp是WindowManager.LayoutParams,它的lp.width和lp.height的默認值是MATCH_PARENT,所以對于DecorView通過getRootMeasureSpec 生成的測量規(guī)格MeasureSpec 的mode是MATCH_PARENT,size是屏幕的高寬,這里我們假設有一個1920x1080像素的手機那么size就對應這兩個值。再根據文章最開頭第一大點中的第(一)小點"根布局的MeasureSpec獲取"可知,當根布局的LayoutParams為Match_parent時,它的MeasureSpec.mode為EXACTLY,用圖像表示為:
第二步,DecorView到screen_simple
??在FrmeLayout的onMeasure()方法中,for循環(huán)遍歷的子View只有兩個:screen_simple,xml和stautsbarbackdroud,前面我們已經說過會跳過狀態(tài)欄的分析,所以這里只看screen_simple,xml對應的View。measureChildWithMargins()中第一個參數child代表的就是screen_simple.xml對應的View,可以看到最終調用了該View的measure()方法,并傳遞進去了DecorView的寬高MeasureSpec。
??我們看看此時screen_simple對應的View的MeasureSpec的兩個參數。由于該View是系統(tǒng)的View,它的LayoutParams默認都是match_parent,又因為它的父布局(DecorVeiw)的mode為EXACTLY,根據我們文章開頭LayoutParams和MeasureSpec的對應關系可知,screen_simple的size為父布局剩余的空間大小,mode為EXACTLY;又因為DecorView是FrameLayout,statusbar是疊在screen_simple上面的,因此相當于狀態(tài)欄不占用底下一層的DecorView的空間——綜上,screen_simple的size等于DecorView的size為1920x1080,mode為EXACTLY。用圖表示:
算出screen_simple.xml對應的View的MeasureSpec之后呢,就開始計算調用measure()方法計算它的大小。由上面層次圖及xml文件可知screen_simple.xml對應的View是一個方向為vertical的LinearLayout,因此child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
最終會調用LinearLayout的onMeasure()方法,(這個過程在上文是有詳細的講解的,如果不記得的話可以翻上去看一下)最終會調用LinearLayout的measureChildBeforeLayout()并在該方法中又一次調用measureChildWithMargins(child, widthMeasureSpec, totalWidth,heightMeasureSpec, totalHeight);
方法,開始計算screen_simple.xml當中各個子元素的大小(子元素測量完之后再測量自身的大小)。
??這里還要注意的一點是screen_simple.xml對應的View有一個padding值:mPaddingTop=100px,這個可能和狀態(tài)欄的高度有關,我們測量的最后會發(fā)現id/statusBarBackground的View的高度剛好等于100px。
第三步,從screen_simple到R.id.content
??這個時候我們進入了screen_simple.xml對應的View中,該View有兩個子元素:R.id.content和VeiwStub。ViewStub上面我們已經說過,它的可見性為GONE,且寬高均為0,所以測量的話會系統(tǒng)會直接跳過。那么我們就只剩下R.id.content了,通過上面的結構層次圖,我們知道這個R.id.content實際上就是我們寫的activity_main.xml文件。
??這里一樣,我們首先需要根據screen_simple.xml對應View的MeasureSpec(size:1980x1080,mode:EXACTLY)和R.id.content的LayoutParams(match_parent x match_parent)來計算出它的MeasureSpec,當然這里的計算也需要遵循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);
}
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;
......
}
這里我們只看高度,之前提到過,screen_simple.xml對應的View有一個padding值:mPaddingBottom=100px,因此getChildMeasureSpec(int spec, int padding, int childDimension)
中第二個參數的值為100px,第三個參數值為match_parent。由int size = Math.max(0, specSize - padding);
可知這里的size變量為(1920-100)x1080 = 1820x1080,又父布局screen_simple.xml的specMode為EXACTLY,childDimension == LayoutParams.MATCH_PARENT,因此最終的結果resultSize = size = 1820x1080;resultMode = MeasureSpec.EXACTLY。用圖表示為:
第四步,activity_main.xml測量規(guī)格MeasureSpec分析
??R.id.content是一個FrameLayout,下面就一個activity_main.xml子元素,這個時候我們同樣先要確定activity_main.xml這個LinearLayout(id/linearLayout)的MeasureSpec值,這里我們再貼一下布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:paddingBottom="50dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hellow word" />
<View
android:id="@+id/selfView"
android:layout_width="match_parent"
android:layout_height="100dp" />
</LinearLayout>
注意id/linearLayout的android:layout_width
,android:layout_height
,這幾個參數的值。父布局screen_simple.xml的MeasureSpec.heightMode = EXACTLY,id/linearLayout的android:layout_height為wrap_content,對照上文的LayoutParams與MeasureSpec可知,id/linearLayout的MeasureSpec.heightMode為AT_MOST;同理,它的MeasureSpec.weightMode為EXACTLY;
??下面我們來看它的specSize,注意:android:layout_marginTop="50dp"
,這里我們需要說明一下Android設備中px/dp的換算——dp x 基準比例 = px; 基準比例 = 系統(tǒng) DPI / 160 ;一般在1920*1080 分辨率的手機上 默認就使用 480 的 dpi ,不管的你的尺寸是多大都是這樣,除非廠家手動修改了配置文件,因此這里可以得到——基準比例=480 / 160 = 3;px = dp x 基準比例 = 50 x 3 = 150px。(關于更多這方面的知識推薦一片文章[Android] Android開發(fā)中dip,dpi,density,px等詳解感興趣的瀆職可自行參閱)。也就是說,id/linearLayout在高度上少了150px,根據上面getChildMeasureSpec()方法中第二個參數:padding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed可知padding = 150px,又int size = Math.max(0, specSize - padding) = 1670 x 1080。
??所以我們現在可以得出id/linearLayout的MeasureSpec,用圖表示:
第五步,TextView和View的測量分析
1.TextView
??接上面,先看TextView:
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hellow word" />
由它的父布局id/linearLayout的MeasureSpec.mode可知,TextView的MeasureSpec.heightMode為AT_MOST;MeasureSpec.weightMode為EXACTLY;至于它的大小,由于id/linearLayout中有android:paddingBottom="50dp"
,它的子元素的生存空間又被壓縮了50dp也就是150px,所以TextView的Measure.specSize為(1670-150)x1080=1520x1080(注意這個是對TextView的寬高約大小束,可不是正真的大小,不要忘了MeasureSpecd的正真含義)。算出id/textView 的MeasureSpec 后,接下來我們來計算textView的正真大小:textView.measure(childWidthMeasureSpec, childHeightMeasureSpec);跳轉到TextView.onMeasure()方法中(frameworks/base/core/java/android/widget/TextView):
if (heightMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
height = heightSize;
mDesiredHeightAtMeasure = -1;
} else {
int desired = getDesiredHeight(); //desired = 107px
height = desired;
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desired, heightSize); //heightSize = 1520px
}
}
TextView字符的高度(也就是TextView的content高度[wrap_content])測出來等于107px,107px 并沒有超過1980px(允許的最大高度),所以實際測量出來TextView的高度是107px。(這個筆者并沒有親自測,直接拿來主義,但是差距不會太大)
??最終算出id/text 的mMeasureWidth=1080,mMeasureHeight=107px。
2.View
??接下來我們來看這個id/selfView的子View:
<View
android:id="@+id/selfView"
android:layout_width="match_parent"
android:layout_height="100dp" />
由于這里指明了它的高度為100dp也就是300px,加上它的父布局id/linearLayout的MeasureSpec.heightMode為AT_MOST,因此它的MeasureSpec.heightMode為EXACTLY,MeasureSpec.weightMode也為EXACTLY,heightSpecSize為自己的尺寸300px,所以它的specSize為300 x 1080,然后計算View的大小:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
最終算出id/selfView的mMeasureWidth=1080px,mMeasureHeight=300px。
第六步,activity_main.xml自身大小測量分析
??id/linearLayout 的子View的高度都計算完畢了,接下來id/linearLayout就通過所有子View的測量結果計算自己的高寬,之前我們著重分析過LinearLayout的測量過程,這里我們再貼一下關鍵代碼:
mTotalLength += mPaddingTop + mPaddingBottom;
簡單理解就是子View的高度的累積+自己的Padding,也就是107px(TextView) + 300px(View) + 150px(paddingBottom="50dp") = 557px最終算出id/linearLayout的mMeasureWidth=1080px,mMeasureHeight=557px。
第七步,從activity到R.id.content,計算R.id.content自身的大小
第八步,從R.id.content到screen_simple,計算screen_simple自身的大小
第九步,從screen_simple到DecotView,計算DeorView自身的大小
后面三步和第六步同理,邏輯都是計算完了子元素的大小加上自身的padding,計算出自身的大小。
站在巨人的肩膀上摘蘋果:
①Android View的繪制流程
②《Android開發(fā)藝術探索》