ViewStub是一个轻量级的不可见视图,它本身不参与绘制,只作为一个占位符存在,作用是延迟加载布局,只有在需要时才会真正地创建和渲染视图。ViewStu继承自View,默认是不可见的 (GONE),不会参与布局计算,因此它不会影响 UI 的性能,适用于减少不必要的 UI 负担

ViewStub 的使用场景

  • 减少不必要的布局开销:比如一个界面有多个状态(如加载中、错误、无数据等),但某些状态很少出现时,可以用ViewStub来延迟加载对应的布局。
  • 优化性能:当某个 UI 组件不常用时(如一个复杂的自定义视图),可以使用ViewStub避免在 Activity/Fragment 启动时就创建它,从而加快 UI 加载速度。
  • 提高可维护性:将额外的 UI 结构封装在ViewStub中,只有在需要时才会实例化,避免过早创建导致的代码复杂度。

ViewStub 的基本用法

1. 在 XML 布局中定义 ViewStub

<ViewStub
  android:id="@+id/viewStub"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout="@layout/layout_error_view" />
  • android:layout指定要延迟加载的布局文件(这里是layout_error_view.xml)。
  • ViewStub默认是GONE状态,不会影响布局计算。

项目中的用法

<!-- 短剧软广原生样式信息区域 -->
<ViewStub
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:id="@+id/flow_nad_summary_zone_container_stub"
  android:inflatedId="@+id/flow_nad_summary_zone_container"
  android:layout_marginTop="@dimen/dp_10"
  android:layout="@layout/video_flow_nad_summary_zone_view"/>
  • android:inflatedIdandroid:layout分别决定了 ViewStub 加载后的 ID 和具体加载的布局

2. 在代码中加载 ViewStub

ViewStub viewStub = findViewById(R.id.viewStub);
View inflatedView = viewStub.inflate(); // 加载布局并返回根视图
  • inflate()方法会将ViewStub替换成layout_error_view.xml 中定义的真正布局,并返回这个布局的根视图。
  • inflate()调用后,ViewStub本身就不会再存在了。

3. 通过 setVisibility 方式显示 ViewStub

ViewStub viewStub = findViewById(R.id.viewStub);
viewStub.setVisibility(View.VISIBLE); // 会自动调用 inflate()

注意setVisibility(View.VISIBLE)也会触发inflate(),但 inflate 只能执行一次,之后ViewStub就变成了真正的视图,不能再次使用。

ViewStub 的注意事项

  1. inflate()只能调用一次
  • ViewStub 一旦被 inflate(),它自己就不存在了,只剩下加载进来的视图。因此,不能重复调用 inflate(),否则会抛 IllegalStateException
  • 如果需要动态地显示/隐藏,可以直接操作 inflate() 返回的视图,而不是 ViewStub 本身。
ViewStub viewStub = findViewById(R.id.viewStub);
viewStub.setLayoutResource(R.layout.layout_other_view);
View inflatedView = viewStub.inflate();
  1. 不能在 ViewStub 上直接设置监听器
  • ViewStub本身不是ViewGroup,所以不能直接调用setOnClickListener(),但可以对inflate()后的视图进行操作:
ViewStub viewStub = findViewById(R.id.viewStub);
View inflatedView = viewStub.inflate();
Button retryButton = inflatedView.findViewById(R.id.btn_retry);
retryButton.setOnClickListener(v -> {
    Toast.makeText(this, "重试", Toast.LENGTH_SHORT).show();
});
  1. ViewStub不支持<merge>标签作为根布局
  2. 不能在ViewStub上设置除layout_*外的其他属性
  3. 动态设置 ViewStub 的布局
ViewStub viewStub = findViewById(R.id.viewStub);
viewStub.setLayoutResource(R.layout.layout_other_view);
View inflatedView = viewStub.inflate();

业务使用

手百封装了一层通用的 Wrapper

class NadViewOptStubWrapper<VIEW : View> constructor(root: View, @IdRes stubId: Int, @IdRes viewId: Int) :
    NadViewOptAbsWrapper<VIEW>(root, stubId, viewId) {
 
    /** view stub */
    private val viewStub: ViewStub = root.findViewById(stubId)
    /** real view */
    private lateinit var theRealView: VIEW
 
    override fun getRealView(): VIEW {
        return if (this::theRealView.isInitialized) {
            theRealView
        } else {
            theRealView = viewStub.inflate() as VIEW
            theRealView
        }
    }
 
    override fun hasInflated(): Boolean {
        return this::theRealView.isInitialized
    }
 
    override fun setVisibility(visibility: Int) {
        if (this::theRealView.isInitialized) {
            theRealView.visibility = visibility
        } else {
            if (visibility == View.GONE) {
                // do nothing
            } else {
                this.getRealView().visibility = visibility
            }
        }
    }
 
    override fun getLayoutParams(): ViewGroup.LayoutParams {
        return if (this::theRealView.isInitialized) {
            theRealView.layoutParams
        } else {
            viewStub.layoutParams
        }
    }
}

使用

 /** 13.75 短剧软广原生样式 */
private val summaryZoneWrapper: NadViewOptStubWrapper<NadSummaryZoneView> by lazy {
    NadViewOptStubWrapper(this, R.id.flow_nad_summary_zone_container_stub, R.id.flow_nad_summary_zone_container)
}
 
/**
 * 注册短剧软广简介区的点击事件
 *
 * @param listener 监听器
 */
fun registerSummaryZoneClickListener(listener: NadSummaryZoneView.OnSummaryClickListener?) {
    summaryZoneWrapper.getRealView().summaryClickListener = listener
}

总结

ViewStub 是一个 轻量级的占位符视图,用于 延迟加载 复杂或不常用的 UI 组件,以提高应用性能。它默认不可见,只有在 inflate()setVisibility(View.VISIBLE) 时,才会真正地加载其布局。适用于多状态 UI、复杂布局的优化等场景。

最佳实践

更新: 2025-08-13 22:56:01
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/epxwcwhggg3gyub1


相关笔记