ViewStub 讲解

一、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:idViewStub 自身的 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 界面

五、重要注意事项

  1. 只能 inflate 一次ViewStub 被替换后就不存在了,多次调用 inflate() 会抛出异常

  2. 不支持 <merge> 标签 :被引用的布局文件中不能使用 <merge> 作为根标签

  3. 布局参数规则 :inflate 后,布局的宽高参数以 ViewStub 中定义的为准

  4. ID 有效期android:id 仅在 inflate 前有效,inflate 后应使用 android:inflatedId 来访问

相关推荐
杉氧1 天前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
召钱熏1 天前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
杉氧1 天前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
通玄1 天前
Jetpack Compose 入门系列(七):ViewModel 与界面状态管理
android
落魄Android在线炒饭1 天前
Android Framework 开发技巧:android.jar 生成与系统快速编译验证
android
如此风景1 天前
Kotlin Flow操作符学习
android·kotlin
plainGeekDev1 天前
GreenDAO → Room
android·java·kotlin
weiggle1 天前
第八篇:ViewModel + Compose——生产级状态管理实践
android
恋猫de小郭1 天前
Amper 正式转正 Kotlin Toolchain ,Gradle 未来何去何从
android·前端·flutter
plainGeekDev1 天前
ButterKnife → ViewBinding
android·java·kotlin