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:inflatedId和android: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 的注意事项
inflate()只能调用一次
ViewStub一旦被inflate(),它自己就不存在了,只剩下加载进来的视图。因此,不能重复调用inflate(),否则会抛IllegalStateException。- 如果需要动态地显示/隐藏,可以直接操作
inflate()返回的视图,而不是ViewStub本身。
ViewStub viewStub = findViewById(R.id.viewStub);
viewStub.setLayoutResource(R.layout.layout_other_view);
View inflatedView = viewStub.inflate();- 不能在
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();
});ViewStub不支持<merge>标签作为根布局- 不能在
ViewStub上设置除layout_*外的其他属性 - 动态设置 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