一、ViewStub 是什么
ViewStub 是 Android 提供的一个轻量级、不可见且不占布局空间 的视图控件,它本身不绘制任何内容,也不参与布局测量。它的核心作用是作为"占位符",在需要时才加载指定的布局文件,从而实现延迟加载(Lazy Loading)。
主要优势:
- 提升启动速度:避免一次性加载所有复杂布局,加快页面渲染
- 节省内存:只有在真正需要时才会 inflate 布局,减少资源占用
二、工作原理
- 页面初始化时,只有
ViewStub这个轻量级占位符被加载(几乎不耗资源) - 当用户触发某个操作(如点击按钮)时,才调用
inflate()或setVisibility(VISIBLE) - 此时,
ViewStub会被它引用的真实布局替换掉,并从视图树中移除自身
注意 :
ViewStub一旦被 inflate 就无法再次调用,因为它在视图树中已经被替换了,重复调用会抛出异常。
三、如何使用
步骤 1:定义要延迟加载的布局
例如,创建一个 layout/loading_layout.xml 文件:
xml
<?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="wrap_content"
android:orientation="vertical">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加载中..."
android:layout_gravity="center" />
</LinearLayout>
步骤 2:在主布局中使用 ViewStub
xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 主内容 -->
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- ViewStub 占位符 -->
<ViewStub
android:id="@+id/stub_loading"
android:inflatedId="@+id/panel_loading"
android:layout="@layout/loading_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
关键属性说明:
android:id:ViewStub自身的 ID,在 inflate 前用于查找它android:inflatedId:替换后的布局根视图的 ID,inflate 后可通过此 ID 访问android:layout:指向要延迟加载的布局文件
步骤 3:在代码中触发加载
方法一:调用 inflate()(推荐,可直接获取加载后的视图)
kotlin
val viewStub = findViewById<ViewStub>(R.id.stub_loading)
val inflatedView = viewStub.inflate() // 返回加载后的布局根视图
// 可以直接操作加载后的视图
val progressBar = inflatedView.findViewById<ProgressBar>(...)
java
ViewStub viewStub = findViewById(R.id.stub_loading);
View inflatedView = viewStub.inflate(); // 返回加载后的布局根视图
方法二:设置 visibility
kotlin
findViewById<View>(R.id.stub_loading).visibility = View.VISIBLE
java
findViewById(R.id.stub_loading).setVisibility(View.VISIBLE);
两种方式效果相同,inflate() 方法会直接返回加载后的 View 对象,省去再调用一次 findViewById() 的麻烦。
步骤 4:监听加载完成(可选)
kotlin
viewStub.setOnInflateListener { stub, inflated ->
// 布局加载完成后的回调
// 可以在这里进行额外的初始化操作
val textView = inflated.findViewById<TextView>(R.id.text_view)
textView.text = "动态设置的内容"
}
java
viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
// 布局加载完成后的回调
TextView textView = inflated.findViewById(R.id.text_view);
textView.setText("动态设置的内容");
}
});
四、常见应用场景
| 场景 | 说明 |
|---|---|
| 列表空视图 | 当 ListView/RecyclerView 数据为空时才显示"暂无数据"提示,大部分情况下不需要加载 |
| 网络错误提示 | 仅在网络请求失败时才显示重试按钮或错误信息 |
| 高级设置面板 | 用户点击"更多设置"时才加载复杂的配置界面 |
| 引导/教程视图 | 首次打开 App 时才显示引导页,后续启动无需加载 |
| 进度条/加载动画 | 仅在执行耗时操作时才显示 loading 界面 |
五、重要注意事项
-
只能 inflate 一次 :
ViewStub被替换后就不存在了,多次调用inflate()会抛出异常 -
不支持
<merge>标签 :被引用的布局文件中不能使用<merge>作为根标签 -
布局参数规则 :inflate 后,布局的宽高参数以
ViewStub中定义的为准 -
ID 有效期 :
android:id仅在 inflate 前有效,inflate 后应使用android:inflatedId来访问