[关闭]
@universal 2018-05-15T06:50:33.000000Z 字数 4130 阅读 411

LinearLayout和RelativeLayout对比

view


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

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


首先来看RelativeLayout的OnMeasure()

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. ......
  4. View[] views = mSortedHorizontalChildren;
  5. int count = views.length;
  6. for (int i = 0; i < count; i++) {
  7. View child = views[i];
  8. if (child.getVisibility() != GONE) {
  9. LayoutParams params = (LayoutParams) child.getLayoutParams();
  10. int[] rules = params.getRules(layoutDirection);
  11. applyHorizontalSizeRules(params, myWidth, rules);
  12. //第一次
  13. measureChildHorizontal(child, params, myWidth, myHeight);
  14. if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
  15. offsetHorizontalAxis = true;
  16. }
  17. }
  18. }
  19. views = mSortedVerticalChildren;
  20. count = views.length;
  21. final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
  22. for (int i = 0; i < count; i++) {
  23. final View child = views[i];
  24. if (child.getVisibility() != GONE) {
  25. final LayoutParams params = (LayoutParams) child.getLayoutParams();
  26. applyVerticalSizeRules(params, myHeight, child.getBaseline());
  27. //第二次
  28. measureChild(child, params, myWidth, myHeight);
  29. if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
  30. offsetVerticalAxis = true;
  31. }
  32. if (isWrapContentWidth) {
  33. if (isLayoutRtl()) {
  34. if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
  35. width = Math.max(width, myWidth - params.mLeft);
  36. } else {
  37. width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
  38. }
  39. } else {
  40. if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
  41. width = Math.max(width, params.mRight);
  42. } else {
  43. width = Math.max(width, params.mRight + params.rightMargin);
  44. }
  45. }
  46. }
  47. if (isWrapContentHeight) {
  48. if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
  49. height = Math.max(height, params.mBottom);
  50. } else {
  51. height = Math.max(height, params.mBottom + params.bottomMargin);
  52. }
  53. }
  54. if (child != ignore || verticalGravity) {
  55. left = Math.min(left, params.mLeft - params.leftMargin);
  56. top = Math.min(top, params.mTop - params.topMargin);
  57. }
  58. if (child != ignore || horizontalGravity) {
  59. right = Math.max(right, params.mRight + params.rightMargin);
  60. bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
  61. }
  62. }
  63. }
  64. ......
  65. }

上面代码只截取了核心的一部分,可以看到relativelayout对子view进行了两次measure,因为相对布局的子view是基于相对位置分布的,子view之间可能分别存在横向和纵向上依赖关系,所以需要在横向和纵向分别进行一次measure来确定view的位置,最后都会调用child.measure();

再来看LinerLayout的OnMeasure:

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

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

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

上面只截取了部分核心代码,可以看到如果子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)。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注