转载请注明出处:http://blog.csdn.net/mybeta/article/details/39547141
LinearLayout是大家最喜欢的Android原生控件之一,相信大家对LinearLayout中的weight这一属性也不陌生,如果这个属性使用的好,能帮我们做出更加漂亮的界面。
比如下面的布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button1" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button2" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button3" />
</LinearLayout>
我们会得到这样一个界面:
三个按钮平均分配空间。这是我们最常用的一种使用方式,也是最简单的使用方式。所以大家一般都认为,weight的作用就是按比例来分配空间。这种说法在某些情况(比如上面的情况)下是正确的,但是并不全面,网上很多其他博客也指出了这一观点的问题所在,而且很多博客也给出了具体的weight值计算公式。但是我们依然需要了解weight到底是如何工作的。
在上面的布局文件中,我们的目的其实就是想让三个Button平均分配空间,也许大家注意到了上面三个Button的layout_width属性都是设置的0dp,为什么要这么设置呢?因为如果设置成别的值的话,也许结果就不是我们所期待的了,至于原因我们会在后面分析到。
weight属性显然影响了LinearLayout中子控件的大小,即影响了measure过程,所以我们定位到LinearLayout中的onMeasure()方法:【以下源码均基于Android2.2】
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
由于LinearLayout区分了水平方向和垂直方向,我们这里就拿垂直方向来分析。我们可以分为两个步骤来分析该过程:
步骤1:
measureVertical()方法比较长,我们分段来分析。该过程可以分为两个部分,我们先来看第一个部分:【注意:该方法中有部分内容是处理LinearLayout宽度的,由于我们这里分析的是高度上的处理过程,所以就忽略了对宽度处理的分析】
mTotalLength = 0;
int maxWidth = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
// 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;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight; // 记录下总的weight值
// 这三个条件同时满足才会不处理child
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.
// 对于lp.weight>0的child,暂时不处理
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
} else {
int oldHeight = Integer.MIN_VALUE;
// 把lp.height设为WRAP_CONTENT
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 0
oldHeight = 0;
lp.height = 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).
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
// 恢复lp.height
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) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
/**
* 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) {
throw 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.
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
allFillParent = 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.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
第1~18行是拿到或设置一些初始值,第1行中的mTotalLength指的是LinearLayout中所有child view的高度和,注意不是LinearLayout的高度。11行拿到高度的measure 模式。
进入21行的for循环,这里对LinearLayout的每一个child view依次进行处理。29行中跳过了visibility为GONE的child view。34行拿到了child view的LayoutParams,因为是在LinearLayout中,所以该LayoutParams的具体类型为LinearLayout.LayoutParams。36行中记录下总的weight值,注意如果在XML文件中没有设置layout_weight属性,那该child view的weight值为0。
接下来是关键部分。
我们先看39行的if条件,heightMode == MeasureSpec.EXACTLY代表LinearLayout的layout_height为MATCH_PARENT或者具体值(注意这并不是一个很好的解释,如果要了解这个值,需要去了解mode值的设置)。lp.height == 0 && lp.weight > 0就是指child view的layout_height为0,并且layout_weight>0。如果同时满足这三个条件,我们可以看到if语句里面对该child view没有进行任何的处理。
如果不能同时满足上面三个条件,进入else部分。进入了else,我们就对child view进行了measure处理。48~58行的作用是,如果一个child view设置了layout_weight,也设置了它的layout_height为0,那么为了让它进行一个有意义的measure,先将它的layout_weight设置为,WRAP_CONTENT。然后在69行恢复到原值。那么中间的部分就是对child view的measure过程了。我们看到64行,该方法的具体实现如下:
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
其实它就是调用了child view的measure方法,而调用过后,我们就可以通过73行的getMeasuredHeight()方法拿到child view measure后的高度,74、75行我们会把这个高度记录到mTotalLength中去。要特别注意64行方法中的最后一个参数,它代表的意义是LinearLayout已经有多大的空间被占用了,在对child view measure的过程中,会先计算出LinearLayout可用的剩余空间,然后在这个剩余空间的基础上去决定child view的大小。在这里,如果totalWeight不为0,我们就忽略掉可用的剩余空间,而让该child view在总的可用空间的基础上去决定自身大小。剩下的代码可以忽略。
步骤2:
接下来分析第二部分的代码:
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
/*
* 关键点:计算出当前LinearLayout的最终高度值。注意时当前的LinearLayout。
*
*/
// Reconcile our calculated size with the heightMeasureSpec
heightSize = resolveSize(heightSize, heightMeasureSpec);
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds
/**
* delta<0,意味着空间不够,需要那些weight>0的视图腾出空间,weight越大,需要腾出的空间越大。
* delta>0,意味着空间富余,所以把富余空间分配给那些weight>0的视图。
*/
int delta = heightSize - mTotalLength;
// 如果delta=0,就是说刚刚好占满空间,之前又有满足条件的没有进行measure的child,
// 那这些child就不会再获得空间了。
// 满足的条件是:heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0
if (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;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) { // 只处理weight>0的
// Child said it could absorb extra space -- give him his share
int 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 measured
// 这里就是看child之前有没有measure过,满足(heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0)
// 这个条件的就是没有被measure过的
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
// child was measured once already above...
// base new measurement on stored values
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
}
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
} else {
// child was skipped in the loop above.
// Measure for this first time here
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));
}
}
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));
}
// 新的总高度(LinearLayout内容的高度)
// 这个值在layout过程中还会被用到
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), heightSize);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
这一部分主要是对weight进行处理,我们平时所看到的weight值形成的效果都是在一部分中实现的。第2行就是加上LinearLayout本身的在垂直方向上的padding,mTotalLength这时候是LinearLayout所有内容的高度。接下来需要得到LinearLayout本身的高度,14行得到的heightSize就是LinearLayout本身的高度,我们看一下resolveSize()方法:
public static int resolveSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
result = Math.min(size, specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
该方法很简单,如果LinearLayout的layout_height为MATCH_PARENT,那么最终的高度就是LinearLayout父控件的高度;如果LinearLayout的layout_height为WARP_CONTENT,那么最终的高度就是LinearLayout父控件的高度和LinearLayout中内容的高度中的最小值。(这个说法不准确)
继续看上面的代码,23行得到一个delta值,该值是LinearLayout本身的高度和LinearLayout内容的高度的一个差值,如果delta>0,代表LinearLayout还有多余的空间,delta<0,代表现有的内容已经超出了LinearLayout的容纳范围。27的if语句主要就是对weight值进行处理,根据weight值进行空间的重新分配,如果delta=0,我们认为measure完成了,不对weight值进行其他的处理了。如果totalWeight值小于或者等于0,我们认为没有设置weight值或者设置的不准确,也不需要对weight进行处理了。
28行可以保证weightSum为正数,如果没有在LinearLayout中设置weightSum,那它的默认值为-1。如果进来if语句,就需要对所有的或者部分child view重新分配空间,即需要重新measure,所以31行将mTotalLength重置为0。33行这里又有一个for循环,做的是和第一部分中的for循环类似的工作。76~86行是处理宽度的,暂且不管。我们看到89行,这里又将child.getMeasuredHeight()加到mTotalLength中去了,所以这个for循环也是将LinearLayout中每一个child view measure后,将它们的measuredHeight累加起来。
那么在这之前又对child view做了怎么的处理呢?我们来到最为核心的43 ~74行。从43行的条件可以看出,我们只对那些weight>0的child view进行重新的measure处理,如果该child view设置的weight值小于0,或者没有设置,这里都不需要处理这个child view,会直接来到89行。45行都得到一个share值,该值是通过weight值按比例获取到的,即按比例平分上面的delta值。46、47行保证下一次循环的时候,同样根据weight值按比例分配剩下的delta值。比如A:1,B:2,C:1,D:1,(冒号后面为weight值),delta=10,那么A:1 / 5 * 10 = 2, B:2 / (5 - 1) * (10 - 2) = 4, C:1 / (5 - 1 - 2) * (10 - 2 - 4) = 2, D:1 / (5 - 1 - 2 - 1) * (10 - 2 - 4 - 2) = 2 。其实和按比例分配是一样的。
如果满足57行的条件,即一个child view的weight值大于0,并且在之前经过了measure过程,60行就是在child view已有的高度上加上一个share值,65行重新measure一次,注意这里重新measure的时候使用的是MeasureSpec.EXACTLY,即这个值是确定了的。姑且一看这里没什么可以深究的,但是当share<0的时候呢?显然会减小child view的高度,并且显然可知,如果weight值越大,在这里减小的程度就越大,最终的高度也就越小,这里就和我们平常所认为的weight值越大,控件也越大的观点相反了。我们再看到70行,如果child view在之前没有measure过(即满足第一部分代码中39行的条件),在这里会直接用share值作为child view的高度,注意这里也是MeasureSpec.EXACTLY,如果是这种情况的话,weight值越大,我们的控件也就越高,和我们平常的观点一致。这也是为什么在文章开头的例子中设置所有的Button的layout_wight为0dp的原因。
总结:
对LinearLayout的measure过程做一个总结:
条件:LinearLayout的heightMode=MeasureSpec.EXACTLY,child view的lp.weight>0并且lp.height=0
1. 遍历所有的child view进行measure,暂时跳过满足上面条件的child view。注意如果totalWeight不为0的话,在对child view进行measure的时候是以LinearLayout的高度为基础的。
2. 计算出一个差值delta,将这个值按比例分配到那些weight>0的child view上。如果delta<0,会减小那些weight>0的child view的大小。
3. 代码中并没有不允许weight<0的child view参与运算,但是如果weight<0,可能会出现某些意想不到的情况,建议不要用weight<0的值。
下面通过例子来具体了解weight的作用。
有这样一个布局:【默认布局】
<LinearLayout
android:id="@+id/layout"
android:layout_width="fill_parent"
android:layout_height="200dp"
android:orientation="vertical" >
<TextView
android:id="@+id/blue_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#0707F1"
android:gravity="center_vertical"
android:text="蓝色"
android:textColor="#ffffff" />
<TextView
android:id="@+id/green_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#16E95A"
android:gravity="center_vertical"
android:text="绿色"
android:textColor="#ffffff" />
<TextView
android:id="@+id/yellow_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#FFFF80"
android:gravity="center_vertical"
android:text="黄色"
android:textColor="#000000" />
<TextView
android:id="@+id/black_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#000000"
android:gravity="center_vertical"
android:text="黑色"
android:textColor="#ffffff" />
</LinearLayout>
显示效果:
4个TextView平均分配空间。
修改布局变成如下:【布局1】
<LinearLayout
android:id="@+id/layout"
android:layout_width="fill_parent"
android:layout_height="200dp"
android:orientation="vertical" >
<TextView
android:id="@+id/blue_text"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:background="#0707F1"
android:gravity="center_vertical"
android:text="蓝色"
android:textColor="#ffffff" />
<TextView
android:id="@+id/green_text"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:background="#16E95A"
android:gravity="center_vertical"
android:text="绿色"
android:textColor="#ffffff" />
<TextView
android:id="@+id/yellow_text"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFF80"
android:gravity="center_vertical"
android:text="黄色"
android:textColor="#000000" />
<TextView
android:id="@+id/black_text"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:background="#000000"
android:gravity="center_vertical"
android:text="黑色"
android:textColor="#ffffff" />
</LinearLayout>
显示效果:
结果只显示黄颜色部分,我们从代码的角度分析该结果。首先前两个TextView lp.height不为0,weight>0,所以会进行measure操作,因为它的lp.height>0,所以可知得到的measuredHeight为100(50dp转化而来),同理,第4个TextView也是进行同样的流程,它的measuredHeight也为100。然后看第3个TextView,它在步骤1中即可measure,并且最终measuredHeight为400(200dp转化而来),所以最终delta = 400 - 100 * 3 - 400 = -300。在步骤2中,第1、2、4个TextView的share值分别为:-100,-100,-100,所以最后这三个TextView的高度都为0。而第3个TextView因为没有weight值,它的高度最终就为400。
如果把布局1中的第3个TextView的layout_height改为wrap_content:【布局2】
显示结果:
这里分析过程同上,大家可以自行分析。
再来看一种布局,在默认布局的基础上修改:【布局3】
<LinearLayout
android:id="@+id/layout"
android:layout_width="fill_parent"
android:layout_height="200dp"
android:orientation="vertical" >
<TextView
android:id="@+id/blue_text"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:background="#0707F1"
android:gravity="center_vertical"
android:text="蓝色"
android:textColor="#ffffff" />
<TextView
android:id="@+id/green_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#16E95A"
android:gravity="center_vertical"
android:text="绿色"
android:textColor="#ffffff" />
<TextView
android:id="@+id/yellow_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#FFFF80"
android:gravity="center_vertical"
android:text="黄色"
android:textColor="#000000" />
<TextView
android:id="@+id/black_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#000000"
android:gravity="center_vertical"
android:text="黑色"
android:textColor="#ffffff" />
</LinearLayout>
显示效果:
第1个TextView weight>0,但是lp.height不为0,所以会进行measure,measuredHeight为100(50dp),而后面的3个TextView在步骤1中都不会measure,所以delta = 400 - 100 = 300,在步骤2中,每一个TextView的share都为 300 / 4 = 75,所以第1个TextView最终高度就为100 + 75 = 175,后面3个TextView都为75。
我们再来看一个weight值为负数的,【布局4】:
<LinearLayout
android:id="@+id/layout"
android:layout_width="fill_parent"
android:layout_height="200dp"
android:orientation="vertical" >
<TextView
android:id="@+id/blue_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#0707F1"
android:gravity="center_vertical"
android:text="蓝色"
android:textColor="#ffffff" />
<TextView
android:id="@+id/green_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#16E95A"
android:gravity="center_vertical"
android:text="绿色"
android:textColor="#ffffff" />
<TextView
android:id="@+id/yellow_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#FFFF80"
android:gravity="center_vertical"
android:text="黄色"
android:textColor="#000000" />
<TextView
android:id="@+id/black_text"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="-1"
android:background="#000000"
android:gravity="center_vertical"
android:text="黑色"
android:textColor="#ffffff" />
</LinearLayout>
显示效果:
前3个TextView在步骤1的都会跳过measure过程,第4个TextView在步骤1的时候虽然不会跳过,但是它的lp.height=0,并且没有变成WRAP_CONTENT,参考步骤1中的第51行代码,lp.weight>0时才会设置为WARP_CONTENT。所以它的measuredHeight=0,delta=400,weightSum=1 + 1 + 1 + (-1) = 2,所以在步骤2中,第1个share=400 * 1 / 2 = 200,即第一个TextView的高度为200,第2个share = 200 * 1 / 1 = 200,即第二个TextView高度为200,到第3个TextView的时候,share=0,所以第3个TextView高度为0,至于第4个TextView,在步骤1中得到的高度就为0,而在步骤2中没有对它进行处理,所以它的高度依然为0。
所以,只要我们了解清楚了weight的具体工作流程,就不用记一些计算公式了。当然,我们用的最多的还是【默认布局】。