我们知道在xml布局的时候,LinearLayout下面的子控件可以使用layout_weight属性,那么我们根据源码,来分析下这个属性的一些知识点。
首先,在系统代码attrs.xml,我们可以知道对这个的定义,还有一个跟weight有关的weightSum的定义。
layout_weight属性是跟控件大小相关的,属于measure,所以我们可以看下LinearLayout的onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);//我们选择水平布局来看
}
}
measureHorizontal代码很长,我们只看跟weight相关的代码
voidmeasureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;//LinearLayout已经被使用了的大小
.......
float totalWeight = 0;//LinearLayout的所有子view总的weight
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final boolean isExactly = widthMode == MeasureSpec.EXACTLY;
int usedExcessSpace = 0;
// See how wide everyone is. Also remember max height.
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;
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerWidth;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;//我们useExcessSpace 为false,以为lp.width是-1和-2这两种情况
if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
......
} else {
if (useExcessSpace) {
.......
}
// 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;
measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
heightMeasureSpec, 0);//第一次测量子view的大小,因为有weight,所以会有第二次测量
final int childWidth = child.getMeasuredWidth();
if (useExcessSpace) {
......
}
if (isExactly) {//我们默认LinearLayout是占据屏幕宽度来分析
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 (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerWidth;
}
if (useLargestChild &&//useLargestChild 默认为false
(widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
......
}
// 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);//根据父亲的MeasureSpec参数测量LinearLayout的大小,这里是占据整个屏幕
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//下面的为0,根据注释我们也知道,这个值就是剩余可用空间的大小
+ (mAllowInconsistentMeasurement ? 0 : usedExcessSpace);
if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;//这里mWeightSum 为0,所以remainingWeightSum 就是刚刚遍历所有子view后所有的weight的相加值
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) {
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);//第二次设置子view的大小
}
}
......//这里是测量LinearLayout的大小,不是我们这次的主题
}
所以根据上面的源码后,我们可以这样总结:
1.layout_weight只有LinearLayout的子view可以用
2.所有有layout_weight的子view需要经历两次measure,一次是正常measure,一次是根据linearlayout最后剩余的可用空间结合layout_weight计算其share,然后和原来的大小相加
3.所以,当子view都为match_parent,最后计算后,layout_weight大的反而最后占据的空间小。而wrap_content则相反。
4.例如有两个子view A和B,都是match_parent,layout_weight分别为1,2,所以最后A的空间为1+ 1*(1 - (1+1))/(1+2) = 2/3,而B的空间则是1/3。