ViewGroup 子类 LinearLayout 的measure 流程
在笔记"View和ViewGroup 的measure过程"中已经提到
ViewGroup没有执行具体的测量过程,只是调用child view 的measure()方法.这是因为ViewGroup之类太多,不好统一处理.实际是各个之类去重写onMeasure来自己处理的.下面主要是分析ViewGroup 子类 LinearLayout 的measure 流程.
从
onMeasure()
开始
LinearLayout.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);//垂直布局
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);//水平布局
}
}
我这边这里只分析垂直布局
measureVertical()
的流程.
具体分析见代码中的注释.
LinearLayout.java
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;//子view的高度和
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;//子视图的最大宽度(不包含layout_weight>0的子view)
int weightedMaxWidth = 0;//子视图的最大宽度(仅包含layout_weight>0的子view)
boolean allFillParent = true;//子视图的宽度是否全是fillParent的,用于后续判断是否需要重新计算
float totalWeight = 0; //所有子view的weight之和
//子view的个数(仅包含直接子view,如LinearLayout1中
//分别有Textview1,LinearLayout2,TextView2,
//而LinearLayout2中又有多个子view。此时LinearLayout1的getVirtualChildCount();为3)
final int count = getVirtualChildCount();
//LinearLayout宽度模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//LinearLayout高度模式
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//子view的宽度是否要由父确定。如父LinearLayout为layout_width=wrap_content,
//子view为fill_parent则matchWidth =true
boolean matchWidth = false;
boolean skippedMeasure = false;
//以LinearLayout中第几个子view的baseLine作为LinearLayout的基准线
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.1, this=" + this);
}
// 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);//默认都返回0,扩展预留,此处没用
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);//默认都返回0,扩展预留,此处没用
continue;
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Child in Pass1: " + i + ", child=" + child
+ ", this=" + this);
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
//child 的 LayoutParams
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
final int totalLength = mTotalLength;
//如果LinearLayout高度是已经确定的。并且这个子view的height=0,weight>0,
//则mTotalLength只需要加上margin即可,
//由于是weight>0;该view的具体高度等会还要计算
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1-1: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Skipped, mTotalLength=" + mTotalLength
+ ", totalWeight=" + totalWeight);
}
skippedMeasure = true;
} else {
int oldHeight = Integer.MIN_VALUE;
if (lp.height == 0 && lp.weight > 0) {
// heightMode is either UNSPECIFIED or AT_MOST, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1-2-1: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Modified, LayoutParams.height change to 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).
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {//是否设置了android:measureWithLargestChild
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1-2-2: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Measured, mTotalLength=" + mTotalLength
+ ", totalWeight=" + totalWeight);
}
}
/**
* If applicable, compute the additional offset to the child's baseline
* we'll need later when asked {@link #getBaseline}.
*/
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
// if we are trying to use a child index for our baseline, the above
// book keeping only works if there are no children above it with
// weight. fail fast to aid the developer.
if (i < baselineChildIndex && lp.weight > 0) {//i 不能小于android:baselineAlignedChildIndex的值的同时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.
//如果LinearLayout宽度不是已确定的,如是wrap_content,而子view是FILL_PARENT,
//则做标记matchWidth=true; matchWidthLocally = true;
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);//最大子view的宽度
childState = combineMeasuredStates(childState, child.getMeasuredState());
//子view宽度是否全是FILL_PARENT
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.
*/
//如父width是wrap_content,子是fill_parent,则子的宽度需要在父确定后才能确定。这里并不是真实的宽度
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1: " + i + ", child=" + child
+ ", this=" + this + ", " + "matchWidth=" + matchWidth
+ ", matchWidthLocally=" + matchWidthLocally + ", "
+ "weightedMaxWidth=" + weightedMaxWidth
+ ", alternativeMaxWidth=" + alternativeMaxWidth);
}
i += getChildrenSkipCount(child, i);
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.1, this=" + this + ", "
+ "maxWidth=" + maxWidth + ", weightedMaxWidth=" + weightedMaxWidth
+ ", alternativeMaxWidth=" + alternativeMaxWidth + ", "
+ "mTotalLength=" + mTotalLength + ", totalWeight=" + totalWeight);
}
if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.1.5, this=" + this);
}
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));
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.1.5, this=" + this
+ ", largestChildHeight=" + largestChildHeight
+ ", mTotalLength=" + mTotalLength);
}
}
// 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;
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
int delta = heightSize - mTotalLength;
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.2, this=" + this + ", "
+ "delta=" + delta + ", " + "heightSize=" + heightSize
+ ", mTotalLength=" + mTotalLength);
}
//skippedMeasure=true:如果LinearLayout高度是已经确定的,并且有子view的height=0,weight>0,
if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Child in Pass2: " + i + ", "
+ "child=" + child + ", this=" + this);
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
// Child said it could absorb extra space -- give him his share
int share = (int) (childExtra * delta / weightSum);
weightSum -= childExtra;
delta -= share;
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);
// TODO: Use a field like lp.isMeasured to figure out if this
// child has been previously measured
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
// child was measured once already above...
// base new measurement on stored values
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
}
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-1-1-1: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Measured, share=" + share
+ ", childHeight=" + childHeight
+ ", mTotalLength=" + mTotalLength);
}
} else {
// child was skipped in the loop above.
// Measure for this first time here
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-1-1-2: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Measured, share=" + share
+ ", mTotalLength=" + mTotalLength);
}
}
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
} else {
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-1-2: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Skipped, ChildExtra=" + childExtra
+ ", mTotalLength=" + mTotalLength);
}
}
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;
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.2-1, this=" + this + ", "
+ "maxWidth=" + maxWidth + ", weightedMaxWidth=" + weightedMaxWidth
+ ", alternativeMaxWidth=" + alternativeMaxWidth + ", "
+ "delta=" + delta + ", mTotalLength=" + mTotalLength);
}
// TODO: Should we recompute the heightSpec based on the new total length?
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
// We have no limit, so make all weighted views as tall as the largest child.
// Children will have already been measured once.
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 (sDebugLayout) {
String measureString = (childExtra > 0)
? "status=Measured" : "status=Skipped";
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-2: " + i
+ ", child=" + child + ", this=" + this + ", " + measureString);
}
}
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.2-2, this=" + this + ", "
+ "maxWidth=" + maxWidth + ", weightedMaxWidth=" + weightedMaxWidth
+ ", alternativeMaxWidth=" + alternativeMaxWidth + ", " + "delta=" + delta
+ ", mTotalLength=" + mTotalLength
+ ", useLargestChild=" + useLargestChild);
}
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.3, this=" + this
+ ", uniformWidth=" + getMeasuredWidth() + ", this=" + this);
}
forceUniformWidth(count, heightMeasureSpec);
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.3, this=" + this);
}
}
}
从上面可以看到,实际分配LinearLayout的各个child view 的高度的时候是先分配没有设置weight和height=0属性的控件并执行该child view的measure流程,然后给那些设置了weight>0同时height=0的子child view.这个也就是79行的
skippedMeasure
=
true
最终会在256的if判断中执行的工作.
为了验证,这里创建几个例子分析.
下面的480dip 在我测试的手机上面就是480*3= 1140px.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/doov_ll_root"
android:layout_width="fill_parent"
android:layout_height="480dip" >
<LinearLayout
android:id="@+id/doov_linear1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ff888888"
android:orientation="vertical" >
<TextView
android:id="@+id/doov_id1"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="2"
android:background="#ff765423"
android:text="11111" />
<TextView
android:id="@+id/doov_id2"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:background="#ffff0000"
android:text="aaaaa" />
<TextView
android:id="@+id/doov_id3"
android:layout_width="fill_parent"
android:layout_height="90dip"
android:background="#ff234532"
android:text="2222222" />
</LinearLayout>
</LinearLayout>
先说一下实际的结果:
doov_ll_root:1440
doov_linear1:1440
doov_id3:270
doov_id1:(1440-270)=1170 ;1170*2/3=760
doov_id2:380
计算过程如下:
256行的时候: delta=1170, heightSize=1440, mTotalLength=270
280行-child =0的时候: share=780, mTotalLength=0, delta=1170-780=390
280行-child =1的时候:share=390, mTotalLength=780,delat=0
child =2的时候不能进入257行的判断,不执行其他处理.
上面的布局实际效果图如下:
另外一种情况:
如果将的
doov_id1和
doov_id2
配置改成如下,也就是高度改成match_parent.
这个时候
doov_id1和
doov_id2
高度比例看起来的时候显然不是2:1,反而像是1:2.还是分析一下具体计算步骤:
256行的时候:delta=-1710, heightSize=1440, mTotalLength=3150(
mTotalLength=1440*2+270
)
300行child=0的时候:share=-1140, childHeight=300, mTotalLength=0
300行child=1的时候:share=-570, childHeight=870, mTotalLength=300
其中
doov_id1的高度为:1440+(-1140)=300
doov_id2的高度为:
1440+(-570 )=870
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/doov_ll_root"
android:layout_width="fill_parent"
android:layout_height="480dip" >
<LinearLayout
android:id="@+id/doov_linear1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ff888888"
android:orientation="vertical" >
<TextView
android:id="@+id/doov_id1"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:layout_weight="2"
android:background="#ff765423"
android:text="11111" />
<TextView
android:id="@+id/doov_id2"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ffff0000"
android:text="aaaaa" />
<TextView
android:id="@+id/doov_id3"
android:layout_width="fill_parent"
android:layout_height="90dip"
android:background="#ff234532"
android:text="2222222" />
</LinearLayout>
</LinearLayout>
上面的布局文件实际显示效果如下:
总结:在使用LinearLayout 的时候,如果设置了child view 的weight 属性,最好将对于的view 的height(或者width)属性设置为0dp,这样可以避免实际显示的比例不对,及时将2个child view 的weight都设置了为1,虽然显示的时候可能是1:1 的比例,但是实际却会多measure一次(具体体现在63和102行).所以想相关child view的
height(或者width)属性设置为0dp不仅可以让视图正确显示,还可以提供代码的效率.