在日常开发中我们常常使用LinearLayout作为布局Group,本文从其源码实现出发分析测量流程。大家可以带着问题进入下面的分析流程,看看是否能找到答案。
垂直测量
View的测量入口方法是onmeasure方法。LinearLayout的onMeasure方法根据其方向而做不同的处理。本文我们选择垂直布局测量看下。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation == VERTICAL) {measureVertical(widthMeasureSpec, heightMeasureSpec);} else {measureHorizontal(widthMeasureSpec, heightMeasureSpec);}}
而measureVertical方法整体上分为2个部分。即两次测量。
第一次测量
for (int i = 0; i < count; ++i) {final View child = getVirtualChildAt(i);if (child == null) {mTotalLength += measureNullChild(i);continue;}if (child.getVisibility() == View.GONE) {//GONE的View不用测量,注意INVISIBLE还是会测量的i += getChildrenSkipCount(child, i);continue;}nonSkippedChildCount++;if (hasDividerBeforeChildAt(i)) {//mTotalLength为总的使用高度,这里加上分割线的高度mTotalLength += mDividerHeight;}final LayoutParams lp = (LayoutParams) child.getLayoutParams();//totalWeight为总的weight,这里主要是统计所有使用weight的view的总weight,为后续测量weight>0的View作准备totalWeight += lp.weight;final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {// 优化:不必费心测量仅使用多余空间布局的子视图。如果我们有空间可供分配,这些视图将在稍后进行测量。//这里是指当LinearLayout为精确模式&&子View使用了weight。那么本次就不用测量该View,而只是将其margin统计到已使用的高度上final int totalLength = mTotalLength;mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);skippedMeasure = true;} else {if (useExcessSpace) {// heightMode 为 UNSPECIFIED 或 AT_MOST,并且此子项仅使用多余的空间进行布局。使用 WRAP_CONTENT 进行测量以便我们能够找出视图的最佳高度。测量后,我们将恢复原始高度 0lp.height = LayoutParams.WRAP_CONTENT;}// 确定此子项想要有多大。如果此子项或先前的子项已指定weight权重,则我们允许它使用所有可用空间(如果需要,我们稍后会缩小空间)。final int usedHeight = totalWeight == 0 ? mTotalLength : 0;//测量子View的宽高measureChildBeforeLayout(child, i, widthMeasureSpec, 0,heightMeasureSpec, usedHeight);final int childHeight = child.getMeasuredHeight();if (useExcessSpace) {// 恢复原始高度并记录我们为多余的子项分配了多少空间,以便我们能够匹配精确测量的行为。lp.height = 0;consumedExcessSpace += childHeight;}final int totalLength = mTotalLength;//更新当前总的高度mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +lp.bottomMargin + getNextLocationOffset(child));if (useLargestChild) {//这个largestChildHeight可以理解成所有子View中的高度最大的View的高度,主要是为了在当前View使用weight>0,但是父View的高度不确定的情况下确定子View的高度,比如LinearLayout是wrap_content,largestChildHeight = Math.max(childHeight, largestChildHeight);}}if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {mBaselineChildTop = mTotalLength;}boolean matchWidthLocally = false;if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {// 线性布局的宽度将缩放,并且至少有一个子视图表示它想要匹配我们的宽度。设置一个标志表示当我们知道我们的宽度时,我们至少需要重新测量该视图。matchWidth = true;matchWidthLocally = true;}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) {/** 计算最大宽度*/weightedMaxWidth = Math.max(weightedMaxWidth,matchWidthLocally ? margin : measuredWidth);} else {alternativeMaxWidth = Math.max(alternativeMaxWidth,matchWidthLocally ? margin : measuredWidth);}i += getChildrenSkipCount(child, i);}
从第一测量的源码得知,其第一次测量主要是测量weight==0的子View的宽高,并为第二次测量做准备:统计weight的和、统计已使用的高度(为weight能分配的高度做准备)
统计最大的子View的高度。
第二次测量
//remainingExcess为剩余可以为weight>0的子View分配高度的总和int remainingExcess = heightSize - mTotalLength+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
if (skippedMeasure|| ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {//如果有weight>0的子View。则走这个分支float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;mTotalLength = 0;for (int i = 0; i < count; ++i) {final View child = getVirtualChildAt(i);if (child == null || child.getVisibility() == View.GONE) {continue;}final LayoutParams lp = (LayoutParams) child.getLayoutParams();final float childWeight = lp.weight;if (childWeight > 0) {// 按照权重计算每个weight>0的子View的高度final int share = (int) (childWeight * remainingExcess / remainingWeightSum);remainingExcess -= share;remainingWeightSum -= childWeight;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;}//生成子View的高度MeasureSpec,模式为精确final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(0, childHeight), MeasureSpec.EXACTLY);final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,lp.width);//重新测量子Viewchild.measure(childWidthMeasureSpec, childHeightMeasureSpec);// Child may now not fit in vertical dimension.childState = combineMeasuredStates(childState, child.getMeasuredState()& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));}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;// 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));}}}}