LinearLayout 作为常用的布局之一,分为水平方向和垂直方向,另外我们经常用到下面的用法 使多个子View均分宽度或高度
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/test_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Hello World!" />
<TextView
android:id="@+id/test_tv2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Hello World2!" />
</LinearLayout>
下面我们来看一下Mearsure 过程:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
//在垂直方向测量
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
// 在水平方向测量
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
下面我们来分析一种measureHorizontal就可以 ,源代码如下:
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;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerWidth;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
// 判断子View 是否设置了layout_weight >0,layout_width = 0
final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;
// 判断LinearLayout 的宽度是否是固定大小或者Match_Parent,同时useExcessSpace
if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
if (isExactly) {
// 此时View没有测量,总宽度为所有view的左右margin相加
mTotalLength += lp.leftMargin + lp.rightMargin;
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength +
lp.leftMargin + lp.rightMargin);
}
// Baseline alignment requires to measure widgets to obtain the
// baseline offset (in particular for TextViews). The following
// defeats the optimization mentioned above. Allow the child to
// use as much space as it wants because we can shrink things
// later (and re-measure).
if (baselineAligned) {
final int freeWidthSpec = MeasureSpec.makeSafeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED);
final int freeHeightSpec = MeasureSpec.makeSafeMeasureSpec(
MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED);
child.measure(freeWidthSpec, freeHeightSpec);
} else {
skippedMeasure = true;
}
} else {
if (useExcessSpace) {
// The widthMode 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 width. We'll restore the original width of 0
// after measurement.
//view设置了layout_width = 0,layout_weight > 0,但是LinearLayout宽度不固定或者不是Match_Parent
// 则设置view的宽度为wrap_content
lp.width = 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).
final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
//测量view
measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
heightMeasureSpec, 0);
final int childWidth = child.getMeasuredWidth();
if (useExcessSpace) {
// Restore the original width and record how much space
// we've allocated to excess-only children so that we can
// match the behavior of EXACTLY measurement.
lp.width = 0;
usedExcessSpace += childWidth;
}
if (isExactly) {
mTotalLength += childWidth + lp.leftMargin + lp.rightMargin
+ getNextLocationOffset(child);
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin
+ lp.rightMargin + getNextLocationOffset(child));
}
if (useLargestChild) {
largestChildWidth = Math.max(childWidth, largestChildWidth);
}
}
boolean matchHeightLocally = false;
if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) {
// The height of the linear layout will scale, and at least one
// child said it wanted to match our height. Set a flag indicating that
// we need to remeasure at least that view when we know our height.
matchHeight = true;
matchHeightLocally = true;
}
final int margin = lp.topMargin + lp.bottomMargin;
final int childHeight = child.getMeasuredHeight() + margin;
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (baselineAligned) {
final int childBaseline = child.getBaseline();
if (childBaseline != -1) {
// Translates the child's vertical gravity into an index
// in the range 0..VERTICAL_GRAVITY_COUNT
final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
& Gravity.VERTICAL_GRAVITY_MASK;
final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
& ~Gravity.AXIS_SPECIFIED) >> 1;
maxAscent[index] = Math.max(maxAscent[index], childBaseline);
maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline);
}
}
maxHeight = Math.max(maxHeight, childHeight);
allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Heights of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxHeight = Math.max(weightedMaxHeight,
matchHeightLocally ? margin : childHeight);
} else {
alternativeMaxHeight = Math.max(alternativeMaxHeight,
matchHeightLocally ? margin : childHeight);
}
i += getChildrenSkipCount(child, i);
}
if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerWidth;
}
// Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
// the most common case
if (maxAscent[INDEX_TOP] != -1 ||
maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
maxAscent[INDEX_BOTTOM] != -1 ||
maxAscent[INDEX_FILL] != -1) {
final int ascent = Math.max(maxAscent[INDEX_FILL],
Math.max(maxAscent[INDEX_CENTER_VERTICAL],
Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
final int descent = Math.max(maxDescent[INDEX_FILL],
Math.max(maxDescent[INDEX_CENTER_VERTICAL],
Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
maxHeight = Math.max(maxHeight, ascent + descent);
}
if (useLargestChild &&
(widthMode == MeasureSpec.AT_MOST || widthMode == 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();
if (isExactly) {
mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
getNextLocationOffset(child);
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
}
}
}
// Add in our padding
mTotalLength += mPaddingLeft + mPaddingRight;
int widthSize = mTotalLength;
// Check against our minimum width
widthSize = Math.max(widthSize, getSuggestedMinimumWidth());
// Reconcile our calculated size with the widthMeasureSpec
int widthSizeAndState = resolveSizeAndState(widthSize, widthMeasureSpec, 0);
widthSize = widthSizeAndState & 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 remainingExcess = widthSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : usedExcessSpace);
// 如果totalWeight > 0 ,重新测量
if (skippedMeasure
|| ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
maxHeight = -1;
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来计算每个view的宽度,
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
//剩余空间等于当前空间减去当前view的宽度
remainingExcess -= share;
// 剩余weight等于当前weightSum-当前view 的weight
remainingWeightSum -= childWeight;
final int childWidth;
if (mUseLargestChild && widthMode != MeasureSpec.EXACTLY) {
childWidth = largestChildWidth;
} else if (lp.width == 0 && (!mAllowInconsistentMeasurement
|| widthMode == MeasureSpec.EXACTLY)) {
// This child needs to be laid out from scratch using
// only its share of excess space.
childWidth = share;
} else {
// This child had some intrinsic width to which we
// need to add its share of excess space.
childWidth = child.getMeasuredWidth() + share;
}
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childWidth), MeasureSpec.EXACTLY);
final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin,
lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// Child may now not fit in horizontal dimension.
childState = combineMeasuredStates(childState,
child.getMeasuredState() & MEASURED_STATE_MASK);
}
if (isExactly) {
mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin +
getNextLocationOffset(child);
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
}
boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
lp.height == LayoutParams.MATCH_PARENT;
final int margin = lp.topMargin + lp .bottomMargin;
int childHeight = child.getMeasuredHeight() + margin;
maxHeight = Math.max(maxHeight, childHeight);
alternativeMaxHeight = Math.max(alternativeMaxHeight,
matchHeightLocally ? margin : childHeight);
allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
if (baselineAligned) {
final int childBaseline = child.getBaseline();
if (childBaseline != -1) {
// Translates the child's vertical gravity into an index in the range 0..2
final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
& Gravity.VERTICAL_GRAVITY_MASK;
final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
& ~Gravity.AXIS_SPECIFIED) >> 1;
maxAscent[index] = Math.max(maxAscent[index], childBaseline);
maxDescent[index] = Math.max(maxDescent[index],
childHeight - childBaseline);
}
}
}
// Add in our padding
mTotalLength += mPaddingLeft + mPaddingRight;
// TODO: Should we update widthSize with the new total length?
// Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
// the most common case
if (maxAscent[INDEX_TOP] != -1 ||
maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
maxAscent[INDEX_BOTTOM] != -1 ||
maxAscent[INDEX_FILL] != -1) {
final int ascent = Math.max(maxAscent[INDEX_FILL],
Math.max(maxAscent[INDEX_CENTER_VERTICAL],
Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
final int descent = Math.max(maxDescent[INDEX_FILL],
Math.max(maxDescent[INDEX_CENTER_VERTICAL],
Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
maxHeight = Math.max(maxHeight, ascent + descent);
}
} else {
alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);
// We have no limit, so make all weighted views as wide as the largest child.
// Children will have already been measured once.
if (useLargestChild && widthMode != 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(largestChildWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
MeasureSpec.EXACTLY));
}
}
}
}
if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
maxHeight = alternativeMaxHeight;
}
maxHeight += mPaddingTop + mPaddingBottom;
// Check against our minimum height
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),
resolveSizeAndState(maxHeight, heightMeasureSpec,
(childState<<MEASURED_HEIGHT_STATE_SHIFT)));
if (matchHeight) {
forceUniformHeight(count, widthMeasureSpec);
}
}
总结:measureHorizontal过程
1 遍历所有的view,判断当前view的参数,LinearLayout 设置了固定宽度或者Match_Parent && layout_width = 0 && layout_weight > 0 ?
如果上面条件成立,则所有的view暂时不测量,每行的已占用宽度为所有View的左右margin之和, 后面会根据layout_weight再次测量
2 如果上述两条不成立,如果宽度不固定或者Wrap_content && layout_width = 0 && layout_weight > 0,
则所有的view宽度为wrap_content
3 真正为 totalWeight > 0.0f这种情况下测量所有的view
// 通常skippedMeasure为true
if (skippedMeasure
|| ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
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;
// layout_weight > 0 单独测量
if (childWeight > 0) {
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;
final int childWidth;
if (mUseLargestChild && widthMode != MeasureSpec.EXACTLY) {
childWidth = largestChildWidth;
} else if (lp.width == 0 && (!mAllowInconsistentMeasurement
|| widthMode == MeasureSpec.EXACTLY)) {
// This child needs to be laid out from scratch using
// only its share of excess space.
childWidth = share;
} else {
// This child had some intrinsic width to which we
// need to add its share of excess space.
childWidth = child.getMeasuredWidth() + share;
}
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childWidth), MeasureSpec.EXACTLY);
final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin,
lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// Child may now not fit in horizontal dimension.
childState = combineMeasuredStates(childState,
child.getMeasuredState() & MEASURED_STATE_MASK);
}
// 所有的view测量完毕,计算总的宽度
if (isExactly) {
mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin +
getNextLocationOffset(child);
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
}
}
总结起来:LinearLayout开始会有一次测量,但是如果LinearLayout的宽度固定或者宽度为Match_Parent,其view的layout_width == 0 && layout_weight > 0,此时不会真正的去测量view的宽高, 后面会通过另一次测量去计算,如果上面条件不满足,则在当前循环中,就会测量view的宽,高
LinearLayout layout 过程,我们也只分析layoutHorizontal过程
void layoutHorizontal(int left, int top, int right, int bottom) {
final boolean isLayoutRtl = isLayoutRtl();
final int paddingTop = mPaddingTop;
int childTop;
int childLeft;
// Where bottom of child should go
final int height = bottom - top;
int childBottom = height - mPaddingBottom;
// Space available for child
int childSpace = height - paddingTop - mPaddingBottom;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final boolean baselineAligned = mBaselineAligned;
final int[] maxAscent = mMaxAscent;
final int[] maxDescent = mMaxDescent;
final int layoutDirection = getLayoutDirection();
// LinearLayout中布局开始时水平方向的left,right
switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {
case Gravity.RIGHT:
// mTotalLength contains the padding already
childLeft = mPaddingLeft + right - left - mTotalLength;
break;
case Gravity.CENTER_HORIZONTAL:
// mTotalLength contains the padding already
childLeft = mPaddingLeft + (right - left - mTotalLength) / 2;
break;
case Gravity.LEFT:
default:
childLeft = mPaddingLeft;
break;
}
int start = 0;
int dir = 1;
//In case of RTL, start drawing from the last child.
if (isLayoutRtl) {
start = count - 1;
dir = -1;
}
for (int i = 0; i < count; i++) {
final int childIndex = start + dir * i;
final View child = getVirtualChildAt(childIndex);
if (child == null) {
childLeft += measureNullChild(childIndex);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
int childBaseline = -1;
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
childBaseline = child.getBaseline();
}
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
//计算每个view的top
switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
childTop = paddingTop + lp.topMargin;
if (childBaseline != -1) {
childTop += maxAscent[INDEX_TOP] - childBaseline;
}
break;
case Gravity.CENTER_VERTICAL:
// Removed support for baseline alignment when layout_gravity or
// gravity == center_vertical. See bug #1038483.
// Keep the code around if we need to re-enable this feature
// if (childBaseline != -1) {
// // Align baselines vertically only if the child is smaller than us
// if (childSpace - childHeight > 0) {
// childTop = paddingTop + (childSpace / 2) - childBaseline;
// } else {
// childTop = paddingTop + (childSpace - childHeight) / 2;
// }
// } else {
childTop = paddingTop + ((childSpace - childHeight) / 2)
+ lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = childBottom - childHeight - lp.bottomMargin;
if (childBaseline != -1) {
int descent = child.getMeasuredHeight() - childBaseline;
childTop -= (maxDescent[INDEX_BOTTOM] - descent);
}
break;
default:
childTop = paddingTop;
break;
}
if (hasDividerBeforeChildAt(childIndex)) {
childLeft += mDividerWidth;
}
childLeft += lp.leftMargin;
setChildFrame(child, childLeft + getLocationOffset(child), childTop,
childWidth, childHeight);
//计算下一个view的水平方向的起始坐标
childLeft += childWidth + lp.rightMargin +
getNextLocationOffset(child);
i += getChildrenSkipCount(child, childIndex);
}
}
}