ViewStub 是 Android 开发中优化布局性能的高频实用工具,它通过让布局按需加载,来解决一系列问题。本文将全面讲解 ViewStub 在使用中必知必会的那些点,还有注意事项。
基础用法
为了演示 ViewStub 的使用,我简单做了个两个布局。
activity_main:
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/inflate_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="INFLATE"/>
<ViewStub
android:id="@+id/view_stub"
android:layout_width="500dip"
android:layout_height="500dip"
android:layout="@layout/target_layout" />
</LinearLayout>
target_layout:
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_green_light">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dip"
android:text="ViewStub Target Layout" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_background" />
</LinearLayout>
当点击按钮时,就会调用 ViewStub 的 inflate() 方法将 target_layout 加载到 Activity 中:
java
public class MainActivity extends Activity {
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button button = findViewById(R.id.inflate_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final ViewStub viewStub = findViewById(R.id.view_stub);
if(viewStub != null)
viewStub.inflate();
}
});
}
}
在加载布局时,虽然布局文件中有 ViewStub,但是它是第一个零宽、零高、不可见的 View,而且在内存中几乎不占据任何空间。任何消耗都在其被调用 inflate 之后。
这里可以看到,只要找到 ViewStub 然后调用 inflate() 方法,就可以加载指定布局。这个布局可以通过 ViewStub 的 android:layout 属性来指定。
这是 ViewStub 最基础的使用,但是它还有一些注意事项。
注意事项
这几个注意事项我先列出来,如下:
- ViewStub 在 inflate 之后就从布局中消失了,无法再被 findViewById
- ViewStub 的 width 和 height 属性无效,是用于传递给目标 layout 的
- 可以通过设置 inflatedId 来设置目标 layout 根布局的 id 属性
- 可以在代码中设置 setLayoutResource 来覆盖 XML 中的 android:layout 属性,指定要加载的布局
- 对 ViewStub 调用 setVisibility(VISIBLE) 与调用 inflate() 的效果是完全一样的
- 要加载的布局不能是 merge 或 include
- 可以通过 setOnInflateListener 来监听加载完成事件
接下对这些注意事项我们就一一说道说道。

前面说过,ViewStub 的核心任务就是 "一次性替换"。当调用 inflate() 方法或将其设置为 VISIBLE 时,它会找到自身所在的父布局,将自己从父布局的 View 树中移除,然后将它指向的 target_layout 加载进来并替换自己的位置。一旦被移除,ViewStub 这个对象就不再存在于当前的 View 层次结构中,自然也就不能再通过任何方式(包括 findViewById)查找和操作它。所以当第二次调用 findViewById 查找时,只能返回 null。
ViewStub 自身在 View 树中是一个 零尺寸 的 View。它的职责是占位,而不是占空间。因此,它自身的宽高设置对它自己不起作用。当 ViewStub 执行替换操作时,它会把自己定义的 android:layout_width 和 android:layout_height 属性,作为 LayuoutParams 传递给被加载的 target_layout 的根 View。这决定了被加载 View 在父容器中的大小和定位。
同理,ViewStub 中的 android:inflatedId 属性,也会覆盖 target_layout 的根 View 的 id 属性。而且这个 target_layout 的根 View 会在 inflate 时返回。
上面说到说可以过 setOnInflateListener 来监听加载完成事件,这里有点要注意,那就是在使用时要注意清空这个 Listener,避免内存泄漏。
java
viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
// inflated 就是被加载进来的 target_layout 的根 View
viewStub.setOnInflateListener(null);
// TODO...
}
});