Android 布局优化--viewstub标签

性能优化之一就是layout的优化,

as 常识:

布局是否合理主要影响的是页面测量时间的多少,我们知道一个页面的显示测量和绘制过程都是通过递归来完成的,多叉树遍历的时间与树的高度h有关,其时间复杂度 O(h),如果层级太深,每增加一层则会增加更多的页面显示时间,所以布局的合理性就显得很重要。

那布局优化有哪些方法呢,主要通过减少层级、减少测量和绘制时间、提高复用性三个方面入手。总结如下:

  • 减少层级。合理使用 RelativeLayout 和 LinerLayout,合理使用Merge。
  • 提高显示速度。使用 ViewStub,它是一个看不见的、不占布局位置、占用资源非常小的视图对象。
  • 布局复用。可以通过includ标签来提高复用。
  • 尽可能少用wrap_content。wrap_content 会增加布局 measure 时计算成本,在已知宽高为固定值时,不用wrap_content 。
  • 删除控件中无用的属性

看下viewstub。这个标签吊的地方是需要显示的时候在加载,其实就是把耗时点移动了,而并不是不耗时。

使用的话develop上有很清晰的讲解:点击打开链接

https://developer.android.com/reference/android/view/ViewStub.html

但是有个小细节,如果深扣或者说完美主义的话:

以develop上的例子:

 <ViewStub android:id="@+id/stub"
               android:inflatedId="@+id/subTree"
               android:layout="@layout/mySubTree"
               android:layout_width="120dip"
               android:layout_height="40dip" />

这里layout也不要写:

 <ViewStub android:id="@+id/stub"
               android:inflatedId="@+id/subTree"
               android:layout_width="120dip"
               android:layout_height="40dip" />

等到需要显示的时候在set,这样更符合优化的初衷。

 
 
viewStub.setLayoutResource(R.layout.mySubTree);
if (view == null) {
    view = viewStub.inflate();
}

其次就是使用注意点,一旦viewstub inflate了或者setvisibility了,则不能再次调用inflate否则会发生crash。


原理也很简单:

1.首先看为什么layout用的时候在设置更好:如果一开始设置了,则会进行加载,不设置则直接0,这就是区别;

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

2.看看这个viewstub如何做到需要的时候在显示的,其他的view为什么直接加载了?

看到1中最后两句setvisibility gone和willnotdraw true;

visibility都知道,gone不显示不占地方,那个后面这个呢,源码如下,也就是设置flag,这样在:

public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

解释如下:也就是不会调用ondraw,从而优化

/**
     * This view won't draw. {@link #onDraw(android.graphics.Canvas)} won't be
     * called and further optimizations will be performed. It is okay to have
     * this flag set and a background. Use with DRAW_MASK when calling setFlags.
     * {@hide}
     */
    static final int WILL_NOT_DRAW = 0x00000080;

不过,都知道,除了ondraw以为还有onmeasure和layout呢,这个也是耗时点:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(0, 0);
}

都0了,很干脆,所以也不用考虑了。

以上这两点就是viewstub没加载的原因。

接下来,看下,inflate或者setvisibility怎么又显示的?

根据基本知识,就知道,肯定有param和addview等操作了。

@Override
@android.view.RemotableViewMethod
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也是通过inflate,所以一个意思,看inflate:

部分code:

final ViewParent viewParent = getParent();

if (viewParent != null && viewParent instanceof ViewGroup) {
    if (mLayoutResource != 0) {
        final ViewGroup parent = (ViewGroup) viewParent;
        final LayoutInflater factory;
        if (mInflater != null) {
            factory = mInflater;
        } else {
            factory = LayoutInflater.from(mContext);
        }
        final View view = factory.inflate(mLayoutResource, parent,
                false);

        if (mInflatedId != NO_ID) {
            view.setId(mInflatedId);
        }

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

1.必须有parent,否则没法添加(合理,本来就是某个布局里的某个控件或者子布局没加载)

2.inflate layout,所以在inflate之前设置layout就ok。

factory.inflate(mLayoutResource, parent,

3.从parent中移除之前(0,0)大小的自己,在重新把解析完了带param或者不带param的自己add进去。



猜你喜欢

转载自blog.csdn.net/shi_xin/article/details/79819214