RelativeLayout和LinearLayout是Android中常用的布局,两者的使用会极大的影响程序生成每一帧的性能,因此,正确的使用它们是提升程序性能的重要工作。下面将通过分析它们的源码来探讨其View绘制性能,并得出其正确的使用方法。

  RelativeLayout和LinearLayout是如何进行measure的?

  通过官方文档我们知道View的绘制进行measure, layout, draw,分别对应onMeasure(), onLayout, onDraw(),而他们的性能差异主要在onMeasure()上。首先是RelativeLayout:
 1 @Override
 2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 3 ......
 4 View[] views = mSortedHorizontalChildren;
 5 int count = views.length;
 6 
 7 for (int i = 0; i < count; i++) {
 8     View child = views[i];
 9     if (child.getVisibility() != GONE) {
10         LayoutParams params = (LayoutParams) child.getLayoutParams();
11         int[] rules = params.getRules(layoutDirection);
12 
13         applyHorizontalSizeRules(params, myWidth, rules);
14         measureChildHorizontal(child, params, myWidth, myHeight);
15 
16         if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
17             offsetHorizontalAxis = true;
18         }
19     }
20 }
21 
22 views = mSortedVerticalChildren;
23 count = views.length;
24 final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
25 
26 for (int i = 0; i < count; i++) {
27     View child = views[i];
28     if (child.getVisibility() != GONE) {
29         LayoutParams params = (LayoutParams) child.getLayoutParams();
30         
31         applyVerticalSizeRules(params, myHeight);
32         measureChild(child, params, myWidth, myHeight);
33         if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
34             offsetVerticalAxis = true;
35         }
36 
37         if (isWrapContentWidth) {
38             if (isLayoutRtl()) {
39                 if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
40                     width = Math.max(width, myWidth - params.mLeft);
41                 } else {
42                     width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
43                 }
44             } else {
45                 if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
46                     width = Math.max(width, params.mRight);
47                 } else {
48                     width = Math.max(width, params.mRight + params.rightMargin);
49                 }
50             }
51         }
52 
53         if (isWrapContentHeight) {
54             if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
55                 height = Math.max(height, params.mBottom);
56             } else {
57                 height = Math.max(height, params.mBottom + params.bottomMargin);
58             }
59         }
60 
61         if (child != ignore || verticalGravity) {
62             left = Math.min(left, params.mLeft - params.leftMargin);
63             top = Math.min(top, params.mTop - params.topMargin);
64         }
65 
66         if (child != ignore || horizontalGravity) {
67             right = Math.max(right, params.mRight + params.rightMargin);
68             bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
69         }
70     }
71 }
72 ......
73 }

 

  根据上述关键代码,RelativeLayout分别对所有子View进行两次measure,横向纵向分别进行一次。
  而LinearLayout:
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 }

 

根据线性布局方向,执行不同的方法,这里分析measureVertical方法。

 1 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
 2 ......
 3 for (int i = 0; i < count; ++i) {
 4     ......
 5 
 6     LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
 7 
 8     totalWeight += lp.weight;
 9     
10     if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
11         // Optimization: don\'t bother measuring children who are going to use
12         // leftover space. These views will get measured again down below if
13         // there is any leftover space.
14         final int totalLength = mTotalLength;
15         mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
16         skippedMeasure = true;
17     } else {
18         int oldHeight = Integer.MIN_VALUE;
19 
20         if (lp.height == 0 && lp.weight > 0) {
21             // heightMode is either UNSPECIFIED or AT_MOST, and this
22             // child wanted to stretch to fill available space.
23             // Translate that to WRAP_CONTENT so that it does not end up
24             // with a height of 0
25             oldHeight = 0;
26             lp.height = LayoutParams.WRAP_CONTENT;
27         }
28 
29         // Determine how big this child would like to be. If this or
30         // previous children have given a weight, then we allow it to
31         // use all available space (and we will shrink things later
32         // if needed).
33         measureChildBeforeLayout(
34                child, i, widthMeasureSpec, 0, heightMeasureSpec,
35                totalWeight == 0 ? mTotalLength : 0);
36 
37         if (oldHeight != Integer.MIN_VALUE) {
38            lp.height = oldHeight;
39         }
40 
41         final int childHeight = child.getMeasuredHeight();
42         final int totalLength = mTotalLength;
43         mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
44                lp.bottomMargin + getNextLocationOffset(child));
45 
46         if (useLargestChild) {
47             largestChildHeight = Math.max(childHeight, largestChildHeight);
48         }
49     }
50     ......

  LinearLayout首先会对所有的子View进行measure,并计算totalWeight(所有子View的weight属性之和),然后判断子View的weight属性是否为最大,如为最大则将剩余的空间分配给它。如果不使用weight属性进行布局,则不进行第二次measure。
 1 // Either expand children with weight to take up available space or
 2 // shrink them if they extend beyond our current bounds. If we skipped
 3 // measurement on any children, we need to measure them now.
 4 int delta = heightSize - mTotalLength;
 5 if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
 6     float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
 7 
 8     mTotalLength = 0;
 9 
10     for (int i = 0; i < count; ++i) {
11         final View child = getVirtualChildAt(i);
12         
13         if (child.getVisibility() == View.GONE) {
14             continue;
15         }
16         
17         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
18         
19         float childExtra = lp.weight;
20         if (childExtra > 0) {
21             // Child said it could absorb extra space -- give him his share
22             int share = (int) (childExtra * delta / weightSum);
23             weightSum -= childExtra;
24             delta -= share;
25 
26             final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
27                     mPaddingLeft + mPaddingRight +
28                             lp.leftMargin + lp.rightMargin, lp.width);
29 
30             // TODO: Use a field like lp.isMeasured to figure out if this
31             // child has been previously measured
32             if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
33                 // child was measured once already above...
34                 // base new measurement on stored values
35                 int childHeight = child.getMeasuredHeight() + share;
36                 if (childHeight < 0) {
37                     childHeight = 0;
38                 }
39                 
40                 child.measure(childWidthMeasureSpec,
41                         MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
42             } else {
43                 // child was skipped in the loop above.
44                 // Measure for this first time here      
45                 child.measure(childWidthMeasureSpec,
46                         MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
47                                 MeasureSpec.EXACTLY));
48             }
49 
50             // Child may now not fit in vertical dimension.
51             childState = combineMeasuredStates(childState, child.getMeasuredState()
52                     & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
53         }
54 
55         ......
56     }
57      ......
58 } else {
59     alternativeMaxWidth = Math.max(alternativeMaxWidth,
60                                    weightedMaxWidth);
61 
62 
63     // We have no limit, so make all weighted views as tall as the largest child.
64     // Children will have already been measured once.
65     if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
66         for (int i = 0; i < count; i++) {
67             final View child = getVirtualChildAt(i);
68 
69             if (child == null || child.getVisibility() == View.GONE) {
70                 continue;
71             }
72 
73             final LinearLayout.LayoutParams lp =
74                     (LinearLayout.LayoutParams) child.getLayoutParams();
75 
76             float childExtra = lp.weight;
77             if (childExtra > 0) {
78                 child.measure(
79                         MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
80                                 MeasureSpec.EXACTLY),
81                         MeasureSpec.makeMeasureSpec(largestChildHeight,
82                                 MeasureSpec.EXACTLY));
83             }
84         }
85     }
86 }
87 ......
88 }

 

  提高绘制性能的使用方式

  根据上面源码的分析,RelativeLayout将对所有的子View进行两次measure,而LinearLayout在使用weight属性进行布局时也会对子View进行两次measure,如果他们位于整个View树的顶端时并可能进行多层的嵌套时,位于底层的View将会进行大量的measure操作,大大降低程序性能。因此,应尽量将RelativeLayout和LinearLayout置于View树的底层,并减少嵌套。

版权声明:本文为hellsong原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/hellsong/p/4613426.html