您的位置:首页 > 移动开发 > Android开发

通过Android源码分析LinearLayout的layout_weight与weightsum对布局大小的影响

2016-12-05 00:54 549 查看
LinearLayout是Android App开发中最常用的控件之一。特别是当我们要实现几个控件平均分割一定的区域的时候,一般都会通过LinearLayout的layout_weight和weightsum组合实现。要理解layout_weight和weightsum这2个属性对LinearLayout布局大小的影响,最好还是结合源码进行分析。

源码分析

LinearLayout有水平和竖直两种方向,在源码中也有对应的2种测量方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}


这里以水平方向为例做个分析:

void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;//占用长度:所有明确指定大小元素长度总和+margin+padding
float totalWeight = 0;//所有子元素的权重和

final int count = getVirtualChildCount();
//一般地,如果LinearLayout的layout_width为match_parent或者指定值,     widthmode为EXACTLY;wrap_content则为AT_MOST
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//是否是精确模式
final boolean isExactly = widthMode == MeasureSpec.EXACTLY;
int usedExcessSpace = 0;
for (int i = 0; i < count; ++i) {//第一次遍历测量子元素
final View child = getVirtualChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;//元素权重总和
final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;//我们暂且称为“0宽权重模式”
//LinearLayout为精确模式并且“0宽权重模式”
if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
if (isExactly) {
mTotalLength += lp.leftMargin + lp.rightMargin;
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength +
lp.leftMargin + lp.rightMargin);
}
} else {//wrap_content情况或非“0宽权重模式”
if (useExcessSpace) {//“0宽权重模式”
lp.width = LayoutParams.WRAP_CONTENT;
}
final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
//第一次预测量子元素
measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
heightMeasureSpec, 0);
final int childWidth = child.getMeasuredWidth();
if (useExcessSpace) {//“0宽权重模式”
lp.width = 0;
usedExcessSpace += childWidth;
}
//计算占用长度
if (isExactly) {//精确模式
mTotalLength += childWidth + lp.leftMargin + lp.rightMargin
+ getNextLocationOffset(child);
} else {//wrap_content情况
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childWidth + 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);//注意这里1,计算“剩余空间”
if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;//注意这里2:如果指定了android:weightSum属性,权重和=android:weightSum指定的值
mTotalLength = 0;
//开始第二次测量子元素
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final float childWeight = lp.weight;
if (childWeight > 0) {
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);//注意这里3:计算子元素在剩余空间占用的份额
remainingExcess -= share;
remainingWeightSum -= childWeight;

final int childWidth;
if (lp.width == 0 && (!mAllowInconsistentMeasurement
|| widthMode == MeasureSpec.EXACTLY)) {
childWidth = share;
} else {
childWidth = child.getMeasuredWidth() + share;
}
//根据childWidth创建childWidthMeasureSpec
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);//注意这里4:真正测量子元素
}

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));
}
}

// Add in our padding
mTotalLength += mPaddingLeft + mPaddingRight;
}
}


从以上代码,我们可以得出:LinearLayout会对所有子元素进行2次测量:第一次预测量是为了计算“剩余空间”;第二次测量是根据weight、weightsum计算在“剩余空间”所占的份额,进而创建measurespec进行测量。

关于weightsum

从上面注意这里2 源码:

float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;


我们可以得出:

未指定android:weightSum属性时,权重和=所有子控件的weight之和;

如果指定了android:weightSum属性,权重和=android:weightSum指定的值,而不是子控件weight和;

关于weight

weight是针对剩余空间的分配,而不是针对LinearLayout总空间的分配;

剩余空间=总宽度-padding-(每个元素的原始宽度+margin+padding)

元素的长度=原始宽度+权重*父视图剩余空间/权重和

例子

测试手机屏幕宽度1440px, dp=4

例1

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">

<View
android:id="@+id/view1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/holo_blue_light" />

<View
android:id="@+id/view2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/holo_green_light" />

<View
android:id="@+id/view3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/holo_orange_light" />
</LinearLayout>


这是最常用的情况:

剩余空间=LinearLayout的总宽度,3个view均分

view1Width=view2Width=view3Width=1440/3=480

例2

view1的宽度改为100dp,其他不变

...
<View
android:id="@+id/view1"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/holo_blue_light" />
...


剩余空间=1440-100*4=1040

view1Width=400+1040/3=746

view2Width=view3Width=1040/3=346

例3

view1的宽度改为100dp,view2/view3宽度为match_parent或wrap_content

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">

<View
android:id="@+id/view1"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/holo_blue_light" />

<View
android:id="@+id/view2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/holo_green_light" />

<View
android:id="@+id/view3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/holo_orange_light" />
</LinearLayout>


剩余空间=1440-100*4-1440-1440=-1840

view1Width=400-1840/3<0 => 0

view2Width=view3Width=1440-1840/3=827

例4

view1/view2/view3宽度为match_parent或wrap_content,view1的layout_weight=2

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">

<View
android:id="@+id/view1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:background="@android:color/holo_blue_light" />

<View
android:id="@+id/view2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/holo_green_light" />

<View
android:id="@+id/view3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/holo_orange_light" />
</LinearLayout>


剩余空间=1440-1440-1440-1440=-2880

view1Width=1440-2880*2/4 = 0

view2Width=view3Width=1440-2880/4=720

另外,提示大家一点的是:可以通过Android Studio的Layout Inspector查看控件的大小

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android layout weightsum