Android 布局优化

一 为什么要进行布局优化

布局优化,我觉得总结起来就是:减少嵌套,避免过度加载。

二 如果能使用linearlayout的,尽量不用RelativeLayout

很多书上介绍这句话的时候,都是简单的一句话带过:因为Relativelayout的功能比较复杂,他的布局过程会花费更多的cpu时间。那么,RelativeLayout为什么会比LinearLayout多耗费cpu时间呢?我们来看Linearlayout的onMeasure()源码

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

好,就一句简单的,判断方向,执行对应的绘制方向,我们挑VERTICAL方向的代码进行分析

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    for (int i = 0; i < count; ++i) {
            ...
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight);
            .....
        }
    if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {

        float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

        mTotalLength = 0;

        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null || child.getVisibility() == View.GONE) {
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final float childWeight = lp.weight;
            if (childWeight > 0) {
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

            }

          
        }

     
    } else {
        alternativeMaxWidth = Math.max(alternativeMaxWidth,                                           weightedMaxWidth);
        if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
            for (int i = 0; i < count; i++) {

                float childExtra = lp.weight;
                if (childExtra > 0) {
                    child.measure(
                            MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                    MeasureSpec.EXACTLY),
                            MeasureSpec.makeMeasureSpec(largestChildHeight,
                                    MeasureSpec.EXACTLY));
                }
            }
        }
    }

好了,我已经把对我们这次分析的主题没有关联的代码删除了。从这段代码我们可以看出,如果我们没有设置LinearLayout的weight属性的话,那么我们的Linearlayout代码只会执行onMeasure一次。
那么,Relativelayout呢?我们接下来来看RelativeLayout的OnMeasure()方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    for (int i = 0; i < count; i++) {
        View child = views[i];
        if (child.getVisibility() != GONE) {
            ...
            measureChildHorizontal(child, params, myWidth, myHeight);
            ...
        }
    }

    for (int i = 0; i < count; i++) {
        final View child = views[i];
        if (child.getVisibility() != GONE) {
            
            measureChild(child, params, myWidth, 
        }
    }
}

由此我们可以看出,RelativeLayout的onMeasure()会执行两次child的measure()方法。

所以,如果对于LinearLayout和ReleativeLayout都能够实现的布局,我们还是推荐使用Linearlayout,但是,如果需要LinearLayout结合其他的layout才能实现的布局,我们就建议还是使用RelativeLayout,因为viewGroup的嵌套使用,也会使减慢布局的绘制。

三 使用标签

1 include标签使用

include标签可以使一个我们已经写好的布局加载到当前的布局中,通过include标签,可以使我们的代码变得简洁,不用再多次重写相同的页面,include标签只支持android:layout_XX开头的属性,其他的属性不支持。


    

2 merge标签使用
merge标签和include一般一起使用,用来减少布局的嵌套。一般来说,如果外面的布局是一个linearlayout,而被包含的布局也是一个linearlayou,那么我们就可以使用merge标签,去减少多余的那一层LinearLayout嵌套






3 ViewStub标签使用
我们来看看ViewStub的注释

/**
 * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate
 * layout resources at runtime.
 *
 * When a ViewStub is made visible, or when {@link #inflate()}  is invoked, the layout resource 
 * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
 * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or
 * {@link #inflate()} is invoked.
**/ 

这段话的意思是,ViewStub是一个不可见的,宽度和高度为0的View,能够通过layout的inflate方法被加载当ViewStub被设置成Visible或者inflate方法被调用,那么,被ViewStub所引用的View资源将会被加载,ViewStub在父布局中的位置将会被所加载的资源所替代。因此,这个时候,viewStub就已经不在了,我们来看看ViewStub的使用:

 

ViewStub标签暂时不支持merge标签。
接下来我们来分析ViewStub的源码,首先我们来看ViewStub的构造方法

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context);

    final TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.ViewStub, defStyleAttr, defStyleRes);
    mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
    mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
    mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
    a.recycle();

    setVisibility(GONE);
    setWillNotDraw(true);
}

我们可以看到,我们在构造方法中,引用了我们在xml中制定的inflatedId,mLayoutResource,然后我们可以看到,viewStub调用了

setVisibility(GONE);
setWillNotDraw(true);

也就是我们上文所说的,ViewStub默认是不可见的,他的宽高是0dp。
接下来,我们来看使ViewStub加载对应资源的两个方法

public View inflate() {
    final ViewParent viewParent = getParent();

    if (viewParent != null && viewParent instanceof ViewGroup) {
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;
            final View view = inflateViewNoAdd(parent);
            replaceSelfWithView(view, parent);

            mInflatedViewRef = new WeakReference<>(view);
            if (mInflateListener != null) {
                mInflateListener.onInflate(this, view);
            }

            return view;
        } else {
            throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
        }
    } else {
        throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
    }
}

从这段代码中,我们大致分析流程,ViewStub先去获取父布局,如果父布局是一个ViewGroup,并且引用的mLayoutResource不为0的话,那么就在父布局中,把自己移除,并把当前加载的布局放到父布局对应的位置中

 private void replaceSelfWithView(View view, ViewGroup parent) {
    final int index = parent.indexOfChild(this);
    parent.removeViewInLayout(this);

    final ViewGroup.LayoutParams layoutParams = getLayoutParams();
    if (layoutParams != null) {
        parent.addView(view, index, layoutParams);
    } else {
        parent.addView(view, index);
    }
}

接下里,我们来看另一个方法,也即是setVisibility(int)方法

public void setVisibility(int visibility) {
    if (mInflatedViewRef != null) {
        View view = mInflatedViewRef.get();
        if (view != null) {
            view.setVisibility(visibility);
        } else {
            throw new IllegalStateException("setVisibility called on un-referenced view");
        }
    } else {
        super.setVisibility(visibility);
        if (visibility == VISIBLE || visibility == INVISIBLE) {
            inflate();
        }
    }
}

所以,当我们第一次设置setVisibility()方法的时候,mInflatedViewRef是空的,所以调用的还是inflate方法。
所以我们可以看到,调用setVisibility方法,和inflate()方法,都是可以起到一样的效果。。。

你可能感兴趣的:(Android 布局优化)