在平时设计UI时,相信大多数人用得比较多的布局就是LinearLayout和RelativeLayout了,毕竟这两种布局能实现我们一般的需求。还记得刚开始学Android时,使用Eclipse进行开发,每次新建一个布局文件,该布局文件默认使用的布局是LinearLayout。后来改用Android Studio开发,SDK版本更新之后,每次新建的布局文件改成用RelativeLayout作为默认布局了。 很多人说这个Google团队出于性能考虑而进行的优化,的确,RelativeLayout能减少布局嵌套从而提高布局的性能,而使用Linearlayout如果不注意的话可能出现布局嵌套层次太深,从而大大降低了布局的性能。但是LinearLayout真的比不上RelativeLayout吗?答案是否定的。
View的绘制一般都是经过onMeasure,onLayout,和onDraw,LinearLayout和RelativeLayout都是继承自ViewGroup,而ViewGroup又是继承View的,所以LinearLayout和RelativeLayout一般也会经历上面所说的三个流程。但是在ViewGroup一般默认不调用onDraw,因为ViewGroup是容器,一般只用来装载控件,所以负责测量控件总共所需的宽高,并将控件放在我们指定的位置就行了。所以接下来我打算只从onMeasure和onLayout两个方面对比LinearLayout和RelativeLayout。
先看看LinearLayout的onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
我们都知道LinearLayout有两个方向走向,一个是竖直方向,一个是水平方向。可以看出Linearlayout在测量宽高时是根据我们设置的方向分别调用不同的测量方法,这两个测量方法大致一样,我们以measureVertical来说明LinearLayout的具体测量过程吧。因为measureVertical这个方法有点长,所以我们贴部分代码说明问题就行了。
// 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 usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
在measureVertical方法里面调用了这个方法,什么意思呢?我们都知道在LinearLayout中,我们可以使用layout_weight属性,当子View使用了layout_weight属性时就会调用这个方法,这个方法的大概步骤是先不限制设置了weight属性的子View的大小,只要容器剩余的空间还足够就不对该子View作处理,但凡事都有个度,所以不可能每个设置了weight属性的子View都无限大吧,所以当所有子View测量完毕后,会对设置了weight属性的子View再调用一次measure方法,如下:
// 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));
}
}
}
接下来说说RelativeLayout的onMeasure方法,部分源码如下:
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);
applyHorizontalSizeRules(params, myWidth, rules);
measureChildHorizontal(child, params, myWidth, myHeight);
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
}
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
applyVerticalSizeRules(params, myHeight, child.getBaseline());
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}
if (isWrapContentWidth) {
if (isLayoutRtl()) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, myWidth - params.mLeft);
} else {
width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
}
} else {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, params.mRight);
} else {
width = Math.max(width, params.mRight + params.rightMargin);
}
}
}
if (isWrapContentHeight) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
height = Math.max(height, params.mBottom);
} else {
height = Math.max(height, params.mBottom + params.bottomMargin);
}
}
if (child != ignore || verticalGravity) {
left = Math.min(left, params.mLeft - params.leftMargin);
top = Math.min(top, params.mTop - params.topMargin);
}
if (child != ignore || horizontalGravity) {
right = Math.max(right, params.mRight + params.rightMargin);
bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
}
}
}
由上面源码我们可知RelativeLayout对子View分别进行了竖直和水平方向的两次测量,这不难理解,因为RelativeLayout里面每个子View的位置都是相对的,所以要分别从水平和竖直两个方向对子View进行测量。
接下来在来看看LinearLayout和RelativeLayout的onLayout方法。同样的,先看LinearLayout的onLayout方法,如下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
简单分析这个方法,即先根据LinearLayout设置的方向调用不同的方法,和上面的onMeasure方法中调用的情况类似,在这两个方法做的事情也差不多,就是将子View逐个按照其大小和布局参数摆放在对应的位置。
再看看RelativeLayout中onLayout方法,如下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
RelativeLayout的onLayout方法和LinearLayout的onLayout方法类似,都是遍历所有的子View,逐个逐个地将子View放在对应的位置,所以两者的onLayout方法性能相差不大。
总结:LinearLayout和RelativeLayout的性能差别主要体在onMeasure方法上,RelativeLayout始终要从竖直和水平两个方向对子View进行测量,而Linearlayout,当我们没有在子View中使用layout_weight属性时,LinearLayout只需对子View进行一次测量,反则需要对子View进行两次测量以确定最终大小,所以如果可以我们尽量少用layout_weight属性。在使用这两个布局之前,我们可以先进行衡量,如果需要实现的布局嵌套层次不深或者嵌套层次已经固定了,可以考虑用LinearLayout,相对的,如果某个布局嵌套层次很深,此时应该考虑使用RelativeLayout来减少嵌套层析从而优化布局的性能。