Android布局优化的一些小技巧

博主的前几篇文章有讲过Android View的加载和绘制流程,这些除了加深我们对Android底层知识的了解之外,也可以在我们自定义View的时候提高姿势水平。那我们在平常使用系统控件、布局的时候有没有一些注意点呢?今天就和大家分享几个我知道的点。
本文要感谢 Android最佳性能实践(四)——布局优化技巧 如何优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析这两篇文章。
首先先说布局,常见的布局有RelativeLayout、LinearLayout、FrameLayout这三种。除了我们知道的RelativeLayout、LinearLayout、FrameLayout他们的特性之外,有其他的使用注意事项吗?
先给大家几个结论,再带大家分析为什么?

  1. RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure,否则一次测量。
    2. RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
  2. 在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
  3. 需要用两层LinearLayout嵌套时,尽量用一个RelativeLayout,此时RelativeLayout耗时更小。另外,LinearLayout慎用layout_weight,会增加一倍耗时操作。

要得出上述结论,方法只有一个——read the fucking source code!
先看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;
        }
    }
}
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,分别在水平和竖直维度上。为什么不能一次完成两个维度上的测量,因为可能存在这样一种依赖,前面的控件A的位置依赖后面的控件B。第一遍测量完后,还得根据后面的测量结果,重新设置前面的View。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

分成水平和竖直两种布局分别实现,随便挑一个竖直方向,看个大概流程(核心功能):

for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);

            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;

            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.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                int oldHeight = Integer.MIN_VALUE;

                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);

                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);
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            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);
        } 

还有很多具体的处理,先忽略了,后面也不会再看了,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值