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 =trueboolean 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 的 LayoutParamsLinearLayout.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 0oldHeight = 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:measureWithLargestChildlargestChildHeight = 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 >0throw 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_PARENTallFillParent = 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 marginsfinal 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 paddingmTotalLength += mPaddingTop + mPaddingBottom;int heightSize = mTotalLength;// Check against our minimum heightheightSize = Math.max(heightSize, getSuggestedMinimumHeight());// Reconcile our calculated size with the heightMeasureSpecint 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 shareint 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 measuredif ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {// child was measured once already above...// base new measurement on stored valuesint 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 herechild.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 paddingmTotalLength += 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 widthmaxWidth = 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" ><LinearLayoutandroid:id="@+id/doov_linear1"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="#ff888888"android:orientation="vertical" ><TextViewandroid:id="@+id/doov_id1"android:layout_width="fill_parent"android:layout_height="0dip"android:layout_weight="2"android:background="#ff765423"android:text="11111" /><TextViewandroid:id="@+id/doov_id2"android:layout_width="fill_parent"android:layout_height="0dip"android:layout_weight="1"android:background="#ffff0000"android:text="aaaaa" /><TextViewandroid: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" ><LinearLayoutandroid:id="@+id/doov_linear1"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="#ff888888"android:orientation="vertical" ><TextViewandroid:id="@+id/doov_id1"android:layout_width="fill_parent"android:layout_height="match_parent"android:layout_weight="2"android:background="#ff765423"android:text="11111" /><TextViewandroid:id="@+id/doov_id2"android:layout_width="fill_parent"android:layout_height="match_parent"android:layout_weight="1"android:background="#ffff0000"android:text="aaaaa" /><TextViewandroid: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不仅可以让视图正确显示,还可以提供代码的效率.
本文详细解析了LinearLayout在Android中的测量流程,重点分析了垂直布局下子View的测量过程,包括如何处理不同权重(weight)的子View。通过具体示例说明了如何避免布局比例不准确的问题。
8921

被折叠的 条评论
为什么被折叠?



