ViewStub的使用

Posted by ijays on July 9, 2017

ViewStub 的使用

介绍

一般在介绍Android 布局优化的时候,都会说到使用include 标签,merge 标签和ViewStub,前两个使用的频率还比较高,后者相对来说使用就不是那么频繁了。前几天刚好做一个error 的界面,并不想一开始就将资源加载进内存中,然后改变visibility,于是就想到了使用ViewStub,这里记录下ViewStub 的使用,方便以后查看。

使用

正如前文所说,ViewStub 最大的特点是按需加载,即一开始并不将加载到内存中,在需要显示的时候,才去渲染整个布局,因此它非常轻量级,几乎可以无视。

下面是ViewStub 中显示的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="113dp"
        android:src="@drawable/calling_overtime" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="30dp"
        android:text="请求超时"
        android:textColor="#484c50"
        android:textSize="32sp" />

</LinearLayout>

xml 中使用ViewStub:

 <Button
        android:id="@+id/bt_view_stub_inflate"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="viewstub inflate" />

    <!-- layout 属性中声明需要加载的布局文件,
         还有一个inflateId属性声明所加载布局文件的根布局id-->
    <ViewStub
        android:id="@+id/view_stub"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="30dp"
        android:layout="@layout/view_stub_layout" />

xml 文件中定义好后,有两种方式来显示ViewStub。

  • visible

调用ViewStub 的setVisibility() 方法来显示这个View,即

mViewStub.setVisibility(View.VISIBLE);
  • inflate

通过ViewStub 的inflate() 方法来显示这个View,即

View inflatedView = mViewStub.inflate();

这两种方式的区别从上面的使用也可以出来了,后者可以获取到inflate 进去的View,然后动态的去修改具体的显示。这里我们可以看下ViewStub 的inflate 实现。

public View inflate() {
        //获取父容器 
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
              //mLayoutResource 即ViewStub 显示的View
                final ViewGroup parent = (ViewGroup) viewParent;
                final LayoutInflater factory;
                if (mInflater != null) {
                    factory = mInflater;
                } else {
                    factory = LayoutInflater.from(mContext);
                }
              //生成需要加载的View
                final View view = factory.inflate(mLayoutResource, parent,
                        false);

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

                final int index = parent.indexOfChild(this);
                //从父布局中移除ViewStub
                parent.removeViewInLayout(this);

                final ViewGroup.LayoutParams layoutParams = getLayoutParams();
              //将显示的View 加入parent 中
                if (layoutParams != null) {
                    parent.addView(view, index, layoutParams);
                } else {
                    parent.addView(view, index);
                }

                mInflatedViewRef = new WeakReference<View>(view);

                if (mInflateListener != null) {
                  //设置inflate 回调
                    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");
        }
    }

从上面的代码中我们我们就可以为之前的结论找到证据了。在调用inflate() 方法后,ViewStub 才会根据设置的layout 生成View,将其add 到父布局中,同时自身也完成使命,将自己的位子移交给inflate 的View。也正因为ViewStub 的这种实现,导致了ViewStub不能inflate 两次,因为在都不在了啊。

最后

基于ViewStub 的特性,适合那种在初始化时不需要显示,只在某些情况下才显示并且频次不高的界面,虽然使用有局限性,但相比初始化就已经加载在布局树上,ViewStub 显然更具效率。