一. 開篇
在最開始接觸 Android 開發(fā)的時(shí)候便學(xué)習(xí)了 LinearLayout 布局控件,它可以在垂直/水平方向依次展開 childView,再配合 weight 屬性使用的話,可以高效、方便地完成許多 UI 界面的開發(fā)。其實(shí) LinearLayout 還有一些其他用法,可能用的不多,可以參考這篇文章 你對(duì)LinearLayout到底有多少了解?(一)-屬性篇
以前就知道,在 LinearLayout 布局時(shí),如果不使用 weight 屬性,LinearLayout 中每個(gè) childView 只會(huì)測(cè)量一次,如果使用 weight 屬性,每個(gè) childView 會(huì)測(cè)量兩次,分析了源碼之后,發(fā)現(xiàn)這種說法也不是十分準(zhǔn)確,childView 會(huì)不會(huì)被測(cè)量兩次,除了依賴是否設(shè)置 android:layout_weight 屬性,還需要依賴其他屬性的
二. 源碼解析
在 LinearLayout 中有垂直/水平兩個(gè)方向的布局,任一方向的布局思想都是相同的,所以我們只需要具體分析其中一個(gè)方向即可,另一個(gè)方向可以類比,在這里我們分析垂直方向的思想
在 View 和 ViewGroup 中的布局有三大流程,分別是 onMeasure、onLayout 和 onDraw,在 LinearLayout 中 onLayout 和 onDraw 兩個(gè)流程基本都是模板化的寫法,而且 LinearLayout 布局簡單,無論是垂直方向還是水平方向都是依次排列每個(gè) childView 的,分析起來并不復(fù)雜,大家可以自行分析。
但是 onMeasure 流程就比較復(fù)雜,分為兩種情況:
- 不使用
layout_weight屬性,每個(gè) childView 按照自身的情況計(jì)算本身的大小即可 - 使用
layout_weight屬性,需要根據(jù) LinearLayout 的剩余空間和layout_weight的比例,計(jì)算每個(gè) childView 的大小
Ok, let's fuck the source code
2.1 非 weight 的情況
2.1.1 布局文件 & 效果
首先,我們來看一個(gè)簡單的布局,xml 文件如下所示
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:text="TextView1"
android:gravity="center"
android:textSize="24sp"
android:textColor="@android:color/white"
android:background="@android:color/holo_green_light"/>
<TextView
android:layout_width="match_parent"
android:layout_height="300dp"
android:text="TextView2"
android:gravity="center"
android:textSize="24sp"
android:textColor="@android:color/white"
android:background="@android:color/holo_blue_light"/>
</LinearLayout>
其中,兩個(gè) TextView 都沒有設(shè)置 layout_weight 屬性,第一個(gè) TextView 的 layout_height 屬性是 200dp,第二個(gè) TextView 的 layout_height 是 300dp,我想這樣簡單的布局只要稍微懂 Android 開發(fā)的人都知道是什么樣的,它的效果如下圖所示,但是說到它的源碼執(zhí)行,不知道又有多少人可以分析得清楚呢?
我們就以這個(gè)簡單的示例,分析 LinearLayout 中的 onMeasure 流程

2.1.2 onMeasure() 執(zhí)行流程
在測(cè)量階段,也就是 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 階段,主要測(cè)量 LinearLayout 的整體大小,以及其中每個(gè) childView 的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
onMeasure(int widthMeasureSpec, int heightMeasureSpec) 源碼如上所示,通過 mOrientation 分別處理垂直和水平兩個(gè)方向的測(cè)量,其中的 mOrientation 變量則是我們?cè)?xml 布局文件中通過 android:orientation="vertical" 或者直接通過 setOrientation(@OrientationMode int orientation) 方法設(shè)置的 LinearLayout 文件方向變量
我們僅分析垂直方向的測(cè)量方法,也就是 measureVertical(int widthMeasureSpec, int heightMeasureSpec)(水平方向的測(cè)量方法 measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) 是類似的原理,有興趣的朋友可以自己分析)。measureVertical 方法還是很長的,不過整個(gè)過程可以分為三個(gè)階段,為了分析的比較清楚,我們也分階段循序漸進(jìn)的分析
1. 聲明變量
在 measureVertical 開始之前,需要初始化一些類變量 & 聲明一些重要的局部變量,重要的變量我都有注釋
其中,最重要的就是有三類:
- mTotalLength:所有 childView 的高度和 + 本身的 padding,注意:它和 LinearLayout 本身的高度是不同的
- 三個(gè)寬度相關(guān)的變量
- maxWidth:所有 childView 中寬度的最大值
- alternativeMaxWidth:所有 layout_weight <= 0 的 childView 中寬度的最大值
- weightedMaxWidth:所有 layout_weight >0 的 childView 中寬度的最大值
- totalWeight:所有 childView 的 weight 之和
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// 一些重要的變量
mTotalLength = 0; // 所有 childView 的高度和 + 本身的 padding,注意:它和 LinearLayout 本身的高度是不同的
int maxWidth = 0; // 所有 childView 中寬度的最大值
int childState = 0;
int alternativeMaxWidth = 0; // 所有 layout_weight <= 0 的 childView 中寬度的最大值
int weightedMaxWidth = 0; // 所有 layout_weight >0 的 childView 中寬度的最大值
boolean allFillParent = true;
float totalWeight = 0; // 所有 childView 的 weight 之和
final int count = getChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
int consumedExcessSpace = 0;
int nonSkippedChildCount = 0;
}
2. 測(cè)量第一階段
在測(cè)量第一階段會(huì)計(jì)算那些沒有設(shè)置 weight 的 childView 的高度、計(jì)算 mTotleLength,并且計(jì)算三個(gè)寬度相關(guān)的變量的值
在看下面代碼之前,請(qǐng)想想我們上面提到的 xml 布局是什么樣的,我們就按照上面的 xml 布局文件的樣式進(jìn)行分析。其中一些重要的英文注釋,我并沒有去掉,大家可以仔細(xì)思考這些英文注釋,有助于理解
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// 接上面的代碼
// See how tall everyone is. Also remember max width.
// 第一次循環(huán)遍歷,正如上面的英文注釋所說明的意圖所在
for (int i = 0; i < count; ++i) {
// 依次得到每一個(gè) childView
// { 在此 xml 布局中,會(huì)依次得到 TextView1 & TextView2 }
final View child = getChildAt(index);
// { 在此 xml 布局中的 TextView1 & TextView2 都不滿足下面的兩個(gè)條件 }
if (child == null) {
continue;
}
if (child.getVisibility() == View.GONE) {
continue;
}
// 沒有跳過的 childView 個(gè)數(shù)
// { 在此 xml 布局中,nonSkippedChildCount 最終為 2 }
nonSkippedChildCount++;
// 在總高度中加上每一個(gè) Divider 的 height
// { 在此 xml 布局中,沒有設(shè)置 `android:divider` 相關(guān)屬性,跳過此 if 判斷 }
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 計(jì)算總權(quán)重 totalWeight
// { 在此 xml 布局中,兩個(gè) childView 都沒有設(shè)置 `android:layout_weight` 屬性,
// 所以 totalWeight 一直為 0}
totalWeight += lp.weight;
// { 在此 xml 布局中,useExcessSpace 為 false }
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// 符合這種條件的 childView 先跳過測(cè)量,在這里不做測(cè)量計(jì)算
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
// The heightMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal height. We'll restore the original height of 0
// after measurement.
lp.height = LayoutParams.WRAP_CONTENT;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
// 這是非常重要的一個(gè)方法,將會(huì)決定每個(gè) childView 的大小
// 如果此 childView 及在此 childView 之前的 childView 中使用了 weight 屬性,
// 我們?cè)试S此 childView 使用所有的空間(后續(xù)如果需要,再做調(diào)整)
// { 在此 xml 布局中,在調(diào)用時(shí) usedHeight 都是 mTotalLength }
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
// 得到測(cè)量之后的 childView 的 childHeight
// { 在此 xml 中,TextView1 的 childHeight 是 200 dp;
// TextView2 的 childHeight 是 300 dp }
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
// Restore the original height and record how much space
// we've allocated to excess-only children so that we can
// match the behavior of EXACTLY measurement.
lp.height = 0;
consumedExcessSpace += childHeight;
}
// 將此 childView 的 childHeight 加入到 mTotalLength 中
// 并加上 childView 的 topMargin 和 bottomMargin
// getNextLocationOffset 方法返回 0,方便以后擴(kuò)展使用
// { 在此 xml 中,mTotalLength 最后的結(jié)果將是 500 dp }
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
// 下面兩個(gè) if 判斷都和 `android:baselineAlignedChildIndex` 屬性有關(guān)
// 在這里不做分析
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}
boolean matchWidthLocally = false;
// { 在此 xml 中,`android: layout_width` 是 `match_parent`,
// 所以 widthMode 是 `MeasureSpec.EXACTLY`,不會(huì)進(jìn)入此 if 判斷 }
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
matchWidth = true;
matchWidthLocally = true;
}
// 計(jì)算三個(gè)和寬度相關(guān)的變量值
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
// { 在此 xml 布局中,最終都會(huì)走到此 代碼塊 中,matchWidthLocally == false }
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
// 如果存在沒有跳過的 childView 并且需要繪制 end divider 則需要加上 end 位置的 divider 的高度
// { 在此 xml 中,沒有設(shè)置 android:showDividers="end",跳過此 if 代碼塊 }
if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
......
}
在上面的代碼中,我都做了詳細(xì)的注釋,其中有一個(gè)方法調(diào)用非常重要,即 measureChildBeforeLayout() 方法,在此方法中將會(huì)計(jì)算每個(gè) childView 的大小
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
在 measureChildBeforeLayout() 方法中,又調(diào)用 ViewGroup 的 measureChildWithMargins() 方法計(jì)算每個(gè) childView 的大小,在測(cè)量垂直方向的 childView 時(shí),有一個(gè)非常重要的參數(shù)需要注意,即:heightUsed,根據(jù)英文注釋,heightUsed 是指在垂直方向,已經(jīng)被 parentView 或者 parentView 的其他 childView 使用了的空間
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/
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);
}
那么在上面示例的 xml 布局測(cè)量過程中 heightUsed 的值是多少呢?
- 在測(cè)量
TextView1時(shí)heightUsed是0,因?yàn)槭堑谝粋€(gè)測(cè)量的 childView,在垂直方向的空間還沒有被使用 - 在測(cè)量
TextView2時(shí)heightUsed是200 dp,因?yàn)?TextView1已經(jīng)使用了200 dp
3. 測(cè)量第二階段
如果進(jìn)入這個(gè) if 條件,會(huì)進(jìn)行第二次的 for 循環(huán)遍歷 childView,重新計(jì)算 mTotalLength。不過這個(gè) if 條件需要 useLargestChild 為 true,useLargestChild 可以通過 xml 屬性 android:measureWithLargestChild 設(shè)置的,不在本文的討論范圍內(nèi)
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// 接上面的代碼
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
......
}
4. 測(cè)量第三階段
經(jīng)過上面的分析之后,終于來到了最后的一個(gè)階段,在這里會(huì)針對(duì)設(shè)置了 android:layout_weight 屬性的布局,重新計(jì)算 mTotalLength
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// 接上面的代碼
// 加上 LinearLayout 自己的 paddingTop 和 paddingBottom
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// 通過 getSuggestedMinimumHeight() 得到建議最小高度,并和計(jì)算得到的
// mTotalLength 比較取最大值
// { 在此 xml 布局中,并沒有設(shè)置 minHeight 和 background,所以還是取 mTotalHeight 值}
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// 通過 heightMeasureSpec,調(diào)整 heightSize 的大小,具體的過程需要
// 看一下 resolveSizeAndState() 方法的實(shí)現(xiàn)
// {在此 xml 布局中,heightSize 經(jīng)過調(diào)整之后就是 LinearLayout 的大小了,
// 也就是整個(gè)屏幕的高度了 }
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
// 重新計(jì)算有 weight 屬性的 childView 大小,
// 如果還有可用的空間,則擴(kuò)展 childView,計(jì)算其大小
// 如果 childView 超出了 LinearLayout 的邊界,則收縮 childView
// { 在此 xml 布局中,不會(huì)進(jìn)入此 if 語句,直接走 else 代碼塊了,
// 因?yàn)椴环蠗l件,skippedMeasure == false,totalWeight == 0 }
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
......
} else {
// 重新計(jì)算 alternativeMaxWidth
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
// useLargestChild 為 false,不在本文討論范圍內(nèi)
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
child.measure(
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight,
MeasureSpec.EXACTLY));
}
}
}
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// 調(diào)整 width 大小
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 調(diào)用 setMeasuredDimension() 設(shè)置 LinearLayout 的大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
經(jīng)過上面四步的源碼分析,非 weight 情況下的垂直布局 onMeasure() 代碼就分析的差不多了。在不使用 android:layout_weight 屬性時(shí),LinearLayout 的 onMeasure 流程還是比較簡單的,只會(huì)進(jìn)入第一個(gè) for 循環(huán)遍歷所有的 childView 并計(jì)算他們的大小,如果使用了 android:layout_weight 屬性則會(huì)進(jìn)入第三個(gè) for 循環(huán)并再次遍歷所有的 childView,再次重新執(zhí)行 childView 的 measure() 方法
2.2 使用 weight 的情況
2.2.1 布局文件 & 效果
上面分析了不使用 android:layout_weight 的情況,現(xiàn)在來分析下使用 android:layout_weight 的情況,還是通過一個(gè)例子入手,xml 布局如下所示
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:showDividers="end"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:text="TextView1"
android:gravity="center"
android:textSize="24sp"
android:layout_weight="2"
android:textColor="@android:color/white"
android:background="@android:color/holo_green_light"/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:text="TextView2"
android:gravity="center"
android:textSize="24sp"
android:layout_weight="3"
android:textColor="@android:color/white"
android:background="@android:color/holo_blue_light"/>
</LinearLayout>
這也是一個(gè)我們最常見的 LinearLayout 的用法,TextView1 的 android:layout_height="0dp" 且 android:layout_weight="2",TextView2 的 android:layout_height="0dp" 且 android:layout_weight="3",如下圖所示,TextView1 和 TextView2 在垂直方向上,會(huì)以 2 : 3 的比例分配整個(gè)屏幕的高度

1. 聲明變量
還是和上面同樣的思路分析 onMeasure 的流程,由于聲明的變量沒有區(qū)別,我們直接跳過聲明變量,從測(cè)量第一階段開始
2. 測(cè)量第一階段
如果是上面這種布局的 xml 代碼,在第一次 for 循環(huán)遍歷 childView 時(shí),會(huì)標(biāo)記 skippedMeasure = true,并計(jì)算所有的 totalWeight,在第二次 for 循環(huán)遍歷時(shí),重新計(jì)算每個(gè)有 weight 屬性的 childView 的大小
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// 接上面的變量聲明
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
// 遍歷每個(gè) childView,如果滿足下面兩個(gè) if 條件之一,則跳過
// { 在此 xml 布局中,兩個(gè) TextView 都不會(huì)跳過 }
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
// 沒有跳過的 childView 個(gè)數(shù)
// { 在此 xml 布局中,nonSkippedChildCount 最終為 2 }
nonSkippedChildCount++;
// 在總高度中加上每一個(gè) Divider 的 height
// { 在此 xml 布局中,沒有設(shè)置 `android:divider` 相關(guān)屬性,跳過此 if 判斷 }
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 不同的地方開始了
// 計(jì)算總權(quán)重 totalWeight
// { 在此 xml 布局中,TextView1 的 weight == 2,TextView2 的 weight == 3
// 所以最終 totalWeight == 5 }
totalWeight += lp.weight;
// {在此 xml 布局中,遍歷 TextView1 和 TextView2 時(shí),useExcessSpace 均為 true,
// 并且滿足下面的 if 條件判斷,skippedMeasure 賦值為 true }
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// 符合這種條件的 childView 先跳過這個(gè)循環(huán)測(cè)量,將 skippedMeasure 賦值為 true,
// 在后面第三個(gè) for 循環(huán)重新計(jì)算此 childView 大小
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
......
}
// 下面兩個(gè) if 判斷都和 `android:baselineAlignedChildIndex` 屬性有關(guān)
// 在這里不做分析
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
// { 在此 xml 布局中,直到這里都還沒有測(cè)量 childView,所以
// child.getMeasuredWidth() == 0}
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
// { 在此 xml 布局中,weightedMaxWidth 一直為 0 }
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
// { 和上面的作用一樣,在計(jì)算高度時(shí),計(jì)算 endDivider 的高度 }
if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
......
}
3. 測(cè)量第二階段
第二階段的測(cè)量和上面提到的一樣,都是和 android:measureWithLargestChild 屬性設(shè)置相關(guān)的,不在本文的討論范圍之內(nèi)
4. 測(cè)量第三階段
在第三階段的測(cè)量之中,針對(duì)設(shè)置了 android:layout_weight 屬性的布局,重新計(jì)算 mTotalLength
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// 接上面的代碼
// 加上 LinearLayout 自己的 paddingTop 和 paddingBottom
mTotalLength += mPaddingTop + mPaddingBottom;
// { 在此 xml 布局中,經(jīng)過上面的兩次 for 循環(huán)之后 mTotalLength == 0 }
int heightSize = mTotalLength;
// 通過 getSuggestedMinimumHeight() 得到建議最小高度,并和計(jì)算得到的
// mTotalLength 比較取最大值
// { 在此 xml 布局中,并沒有設(shè)置 minHeight 和 background,所以還是取 mTotalHeight 值,
// 所以 heightSize == 0 }
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// 通過 heightMeasureSpec,調(diào)整 heightSize 的大小,具體的過程需要
// 看一下 resolveSizeAndState() 方法的實(shí)現(xiàn)
// { 在此 xml 布局中,heightSize 經(jīng)過調(diào)整之后就是 LinearLayout 的大小了,
// 也就是整個(gè)屏幕的高度了 }
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
// 重新計(jì)算有 weight 屬性的 childView 大小,
// 如果還有可用的空間,則擴(kuò)展 childView,計(jì)算其大小
// 如果 childView 超出了 LinearLayout 的邊界,則收縮 childView
// { 在此 xml 布局中,經(jīng)過上面的第一次 for 循環(huán)之后 skippedMeasure == true,
// remainingExcess == 整個(gè)屏幕的高度,totalWeight == 5 }
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
// 根據(jù) mWeightSum 計(jì)算得到 remainingWeightSum,mWeightSum 是通過
// `android:weightSum` 屬性設(shè)置的,totalWeight 是通過第一次 for 循環(huán)計(jì)算得到的
float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
// 將 mTotalLength 復(fù)位為 0
mTotalLength = 0;
// 開始真正的第二次 for 循環(huán)遍歷每一個(gè) childView,重新測(cè)量每一個(gè) childView
for (int i = 0; i < count; ++i) {
// 得到每一個(gè) childView,如果符合下面的 if 判斷則跳過
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final float childWeight = lp.weight;
// 如果該 childView 設(shè)置了 `weight` 值,則進(jìn)入 if 語句塊
// { 在此 xml 布局中,TextView1 的 layout_weight == 2,
// TextView2 的 layout_weight == 3,都會(huì)進(jìn)入下面的 if 條件判斷 }
if (childWeight > 0) {
// 這是設(shè)置了 weight 的情況下,最重要的一行代碼
// remainingExcess 剩余高度 * ( childView 的 weight / remainingWeightSum)
// share 便是此 childView 通過這個(gè)公式計(jì)算得到的高度,
// 并重新計(jì)算剩余高度 remainingExcess 和剩余權(quán)重總和 remainingWeightSum
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;
// 通過下面的 if 條件重新計(jì)算,childHeight 是最終 childView 的真正高度
// { 在此 xml 布局中,TextView1 和 TextView2 都會(huì)走到第二個(gè)條件中去,
// childHeight == share }
final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
|| heightMode == MeasureSpec.EXACTLY)) {
// This child needs to be laid out from scratch using
// only its share of excess space.
childHeight = share;
} else {
// This child had some intrinsic height to which we
// need to add its share of excess space.
childHeight = child.getMeasuredHeight() + share;
}
// 計(jì)算 childHeightMeasureSpec & childWidthMeasureSpec,并調(diào)用 child.measure() 方法
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
// 重新計(jì)算 maxWidth & alternativeMaxWidth
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
// 考慮 childView.topMargin & childView.bottomMargin,重新計(jì)算 mTotalLength
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// 完成 for 循環(huán)之后,加入 LinearLayout 本身的 mPaddingTop & mPaddingBottom
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
} else {
......
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// 調(diào)整 width 大小
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 調(diào)用 setMeasuredDimension() 設(shè)置 LinearLayout 的大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
三. 小結(jié)
經(jīng)過上面對(duì)兩種情況的分析,其實(shí) onMeasure 流程已經(jīng)比較清晰了,簡單總結(jié)一下,我們可以學(xué)習(xí)到以下幾點(diǎn)
- LinearLayout 的設(shè)計(jì)者有意的對(duì)設(shè)置了
weight和不設(shè)置weight的情況分別處理,通過skippedMeasure變量 &childView.height&childView.weight區(qū)分,從上面我舉的兩個(gè)例子中就可以明顯的感受到,兩種測(cè)量流程分的還是比較詳細(xì)清楚的 - 在 LinearLayout 中總共有 3 個(gè) for 循環(huán),分別處理不同的流程
- 第一個(gè) for 循環(huán),只會(huì)在不使用
weight屬性時(shí)進(jìn)入,并有可能會(huì)測(cè)量每個(gè) childView 的大小 - 第二個(gè) for 循環(huán),在使用
android:measureWithLargestChild時(shí)才會(huì)進(jìn)入,并且即使進(jìn)入也不會(huì)調(diào)用 childView 的測(cè)量方法,只會(huì)更新mTotalLength變量 - 第三個(gè) for 循環(huán),只會(huì)在使用
weight屬性時(shí)進(jìn)入,并測(cè)量每個(gè) childView 的大小
- 第一個(gè) for 循環(huán),只會(huì)在不使用
- 通過上面的分析,即使是使用了
android:layout_weight屬性,childView 也不會(huì)一定就測(cè)量兩次,還需要看android:layout_height和 LinearLayout 的heightMode屬性 - 通過上面的源碼分析,熟悉鞏固了
measureChildWithMargins(...)、resolveSizeAndState(...)、getChildMeasureSpec(...)、setMeasuredDimension(...)等 Api,這些 Api 對(duì)于我們自定義控件還是非常重要的