ViewStub

来源:互联网 发布:腾讯企业邮箱pop 端口 编辑:程序博客网 时间:2024/04/27 20:31

参考

        http://blog.csdn.net/hitlion2008/article/details/6737537。

概述

        ViewStub是一种不可视,并且大小为0的视图,可以延迟到运行时填充(inflate)布局资源。当ViewStub被设置为可视或者inflate()被调用后,就会填充布局资源,然后ViewStub便会被填充的视图替代。
        ViewStub是实现视图延迟加载的优秀类。无论在什么情况下,只要开发者需要根据上下文选择隐藏或者显示一个视图,都可以用ViewStub实现或许并不会因为一个视图的延迟加载而感觉到性能 明显提升,但是如果视图树的层次很深,便会感觉到性能上的差距了。
        以上两段摘自《Android开发必知的50个诀窍》

注意

        一个ViewStub只能inflate()一次
        在布局文件中使用ViewStub时,必须指定layout属性的值。
        ViewStub被inflate()后,它所指向的布局会替换掉它在父布局中的位置。
        它所指向的布局的根元素的一些布局参数,必须在<ViewStub>中进行设置,如:layout_width,layout_height等。
        为<ViewStub>设置的id会被设置为布局文件生成的布局的根元素的id

使用

        在布局中使用ViewStub非常简单,跟使用TextView一样。但必须指定layout属性,且该属性必须指向一个布局文件。
        可以指定inflateId属性,该属性是用来更新布局文件的根元素的id。(Overrides the id of the inflated View with this value)。
        在ViewStub中构造方法中可以发现:指定的layout属性会赋值给ViewStub类中的mLayoutResource变量,inflateId会被赋值给mInflatedId变量。代码如下:
        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
        在代码中调用ViewStub.inflate()就可以显示ViewStub引用的布局。

分析

ViewStub.inflate()

    public View inflate() {        final ViewParent viewParent = getParent();//首先得到ViewStub的父布局        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);//将inflateId属性的值设置成布局文件中的根元素的id。代码五                }                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);                }                mInflatedViewRef = new WeakReference<View>(view);                if (mInflateListener != null) {                    mInflateListener.onInflate(this, view);                }                return view;            } else {//如果没有指定layout属性,这里并报错。因此,必须指定layout属性,无论是通过xml还是代码                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");            }        } else {            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");        }    }
        在代码一处:通过指定的布局资源生成新的View。因此,ViewStub的layout属性必须存在,且必须指向一个布局文件。而且在inflate()的最后会把通过布局文件生成的视图返回。
        在代码三、四处:首先获得ViewStub在父布局中的下标,其次将ViewStub从父布局中移除,然后将布局文件生成的视图添加到父布局中。也就是:用布局文件生成的布局会替换掉ViewStub在父布局中的位置
        通过代码三、四还可以看出:ViewStub一旦被inflate后,就会从父布局中移除掉,因而一个ViewStub只能inflate一次。因为再次inflate时会直接走到最下面的else中。
        在代码四处,将生成的布局添加到ViewStub的父布局时传递了ViewStub自身的LayoutParams。因此布局文件中的根结点的有些布局参数必须在<ViewStub>中设置(如layout_width,layout_height等),但并不是所有的以layout_开头的属性都需要在<ViewStub>中设置。
        在代码五处,将ViewStub的id设置为生成的布局的id

setVisibility()

        调用ViewStub.setVisibility(View.VISIBLE)时,它所执行的效果和ViewStub.inflate()一样。示例如下:
View view = findViewById(R.id.myid);//代码一// view stub replaced with inflated layout (if stub is used in layout)view.setVisibility(View.VISIBLE);//代码二if (view.getParent() == null) {//代码三// a stub was used so we need to find the newly inflated view that// replaced itview = findViewById(R.id.myid);//代码四} else {// nothing to do, the view we found the first time is what we want}
        代码一:R.id.myid是在布局中为ViewStub设置的id。但是这里并没有强转成ViewStub,这是因为代码二处的是任何View都有的方法。
        代码二:无论代码一处得到的是ViewStub还是别的View,该方法都可以被调用。但是,如果是ViewStub,源代码如下:
    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();            }        }    }
        从源代码中可以看出:此时实际上也是调用了inflate()。而inflate()中会将ViewStub的id设置为生成的布局的id。这也保证了在代码四处得到的是最新的生成的布局,而不是ViewStub。
        之所以加上代码三是为了优化代码。如果代码二得到的不是ViewStub,那么代码四处就相当于重复调用,从而影响了性能。在ViewStub.inflate()时,会将ViewStub从它的父布局中移除,因此如果代码一得到的是ViewStub那么代码三才成立,从而执行代码四,保证了得到的View是通过布局文件生成的新布局。

缺点

        由于ViewStub只能inflate一次,因此,对于那些需要多次显示隐藏的控件,只能交替使用GONE与VISIBLE。
0 0