LinearLayout和RelativeLayout绘制过程的对比

在平时设计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来减少嵌套层析从而优化布局的性能。

打赏