参考
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 it
view = 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。