Fork me on GitHub

LinearLayout和RelativeLayout对比

还是来从问题入手:在实现效果相同的情况下,线性布局和相对布局选择哪个?

relativelayout和linerlayout都是继承自ViewGroup,所以要比较view的性能还是要看onMeasure、onLayout、onDraw三个方法。有测试数据表示在加载相同布局情况下,linerlayout的onLayout、onDraw方法的执行时间和relativelayout差不多,但是liner的onMeasure()时间要短很多。既然出现了差异,那么就从源码角度具体分析。


首先来看RelativeLayout的OnMeasure()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
......
View[] views = mSortedHorizontalChildren;
int count = views.length;
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;
}
}
}
views = mSortedVerticalChildren;
count = views.length;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
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进行了两次measure,因为相对布局的子view是基于相对位置分布的,子view之间可能分别存在横向和纵向上依赖关系,所以需要在横向和纵向分别进行一次measure来确定view的位置,最后都会调用child.measure();

再来看LinerLayout的OnMeasure:

1
2
3
4
5
6
7
8
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}

可以看出来linerlayout只针对布局方向进行了一次measure。
具体的看下measureVertical()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
.....
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.height == 0 && lp.weight > 0;
//设置了weight值
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin+lp.bottomMargin);
skippedMeasure = true;//标记位,跳过measure
}else {
....
measureChildBeforeLayout(child,i,widthMeasureSpec,0,heightMeasureSpec,usedHeight);
....
}
}
.....
if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
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 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
//针对weight大于0的view
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
}

上面只截取了部分核心代码,可以看到如果子view的layout:weight>0且layout:height=0的时候,第一次measure跳过这些view,并设置标记位skippedMeasure为true,在后面对这些view进行第二次measure。同理,measureHorizontal()里的逻辑类似。


总结:

相同的布局情况下,relativet会对子View进行两次measure,liner只对子View进行一次measure,而在设置了weight时,也会对weight进行两次measure,所以一般情况下LinearLayout的性能要优于RelativeLayout。
如果view的层级嵌套过多,尽量使用relativelayout来降低层级,因为relative比较灵活而且层级结构比较扁平,很多liner的嵌套都可以用一个relative来代替。这也是Google推荐的方式(之前新建项目的布局都是relativelayout)。