使用ViewStub来提高加载性能吧!

什么是ViewStub?

ViewStub其实本质上也是一个View,其继承关系如图所示:
ViewStub继承关系

为什么ViewStub可以提高加载性能?

ViewStub使用的是惰性加载的方式,即使将其放置于布局文件中,如果没有进行加载那就为空,不像其它控件一样只要布局文件中声明就会存在。
那ViewStub适用于场景呢?通常用于网络请求页面失败的显示。一般情况下若要实现一个网络请求失败的页面,我们是不是使用两个View呢,一个隐藏,一个显示。试想一下,如果网络状况良好,并不需要加载失败页面,但是此页面确确实实已经加载完了,无非只是隐藏看不见而已。如果使用ViewStub,在需要的时候才进行加载,不就达到节约内存提高性能的目的了吗?

ViewStub的加载原理

ViewStub只能加载一次,重复加载会导致异常,这是因为ViewStub只要加载过一次,其自身就会被移除,把并自身所包含的内容全部传给父布局。来张图感受一下
ViewStub的加载原理

ViewStub如何使用

  • 父布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.example.administrator.myviewstub.MainActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="inflate"
        android:text="inflate" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="setData"
        android:text="setdata"/>
    <ViewStub
        android:id="@+id/vs"
        android:layout="@layout/viewstub"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

可以看到ViewStub又引用了另外一个布局。

  • ViewStub布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:id="@+id/hello_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="DATA EMPTY!"/>
</FrameLayout>
  • MainActivity
public class MainActivity extends AppCompatActivity {
    private ViewStub viewStub;
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewStub = (ViewStub) findViewById(R.id.vs);
        //textView  = (TextView) findViewById(R.id.hello_tv);空指针
    }
    public  void inflate(View view){
        viewStub.inflate();
        //textView  = (TextView) viewStub.findViewById(R.id.hello_tv);空指针
        textView  = (TextView) findViewById(R.id.hello_tv);
    }
    public void setData(View view){
        textView.setText("DATA!!");

    }
}

注意:这里有几个坑,前面我们说了ViewStub只有在初始化之后才会存在,所以第一个注释中的空指针是因为ViewStub还未初始化。那第二个注释中的空指针是怎么回事呢?前面我们也说了,ViewStub在加载完成之后会移除自身,并把自身的内容转给父布局,所以此时viewStub中的内容已经不存在了,textView已经是父布局的东西了,所以不能使用viewStub来findViewById。另外,前面我们说了ViewStub只能加载一次,若调用两次inflate()的话会导致异常。
加载异常

为了验证所得出的结论是不是正确的,我截了两张ViewStub加载前后的图。

  • 加载前:
    加载前

从图中可以看到ViewStub还没加载,是灰色的。

  • 加载后:
    加载后

当点击了INFLATE之后,可以看到,ViewStub消失了,取而代之的是一个FrameLayout,其中包含了一个AppCompatTextView(ps.其实就是TextView,只是Google在5.0之后提供了向前兼容,就好比AppCompatActivity和Activity一样)。咳,这个FrameLayout是不是很眼熟,没错!就是我们之前写的ViewStub的布局,忘记的童鞋翻回去看看。

  • 顺便验证一下TextView是不是能用,点击SETDATA:
    这里写图片描述

没有问题。

关于这个图是怎么来的,童鞋们只要点击Android Montior中的Layout Inspector就行啦,就是介个:
Layout Inspector
感兴趣的童鞋可以自己去试试。

源码分析

ViewStub是如何实现的呢,接下来我们来一探究竟:

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

        if (viewParent != null && viewParent instanceof ViewGroup) {
        //若父不是ViewGroup就会抛出异常("ViewStub must have a non-null ViewGroup viewParent")
            if (mLayoutResource != 0) {
            //这个就是ViewStub只能加载一次的原因,第二次加载则抛出异常(throw new IllegalArgumentException("ViewStub must have a valid layoutResource"))
                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);
                //创建一个View,这个View为ViewStub的内容,mLayoutResource为ViewStub自身的Layout资源文件id
                if (mInflatedId != NO_ID) {
                    view.setId(mInflatedId);
                    //若mInflatedId存在,则将id重新赋值给新的View
                }

                final int index = parent.indexOfChild(this);
                parent.removeViewInLayout(this);
                //通过父布局将ViewStub移除
                final ViewGroup.LayoutParams layoutParams = getLayoutParams();
                if (layoutParams != null) {
                    parent.addView(view, index, layoutParams);
                } else {
                    parent.addView(view, index);
                }
                //将ViewStub中的内容添加到父容器中
                mInflatedViewRef = new WeakReference<View>(view);
                //设置为弱引用,当VIewStub设置为空时将立即执行GC
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }

                return view;
                //最后将View返回出去
            } else {
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }

ViewStub中还有一个setVisibility(int visibility)值得我们注意:

public void setVisibility(int visibility) {
        if (mInflatedViewRef != null) {
            View view = mInflatedViewRef.get();
            //拿到关联的Layout
            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();
            }
        }
    }

可以看到,ViewStub若之前没有进行inflate,setVisibility(View.VISIBLE)或setVisibility(View.INVISIBLE)会自动先进行加载,而加载之后可以设置显示和隐藏。并且ViewStub设置的不是自己,而是拿到关联的那个Layout设置visible。ViewStub此时并未销毁,所以建议初始化后将其设置为空。

  • 13
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值