ViewStub 是 Android 提供的一个轻量级 View,它本身不会绘制任何内容,占用 0x0 的大小,主要用于 延迟加载布局。当调用 inflate()
方法或 setVisibility(View.VISIBLE)
使其可见时,它会 替换 自身并加载指定的 layout 资源,从而优化 View 的初始化时间和内存占用。通过ViewStub可以避免初始化不必要的 View,提高加载性能,减少复杂 UI 造成的绘制开销。关于ViewStub的使用可以参见上一篇文章:Android 布局优化:利用 ViewStub 和 Merge 提升性能,本文来看下ViewStub的源码,了解下它的工作流程。
ViewStub源码
ViewStub 继承自 View,但它不会执行 draw(),不会出现在 View Hierarchy,仅在 inflate() 之后替换自身。关键代码如下:
kotlin
private int mInflatedId; // 替换后 View 的 ID
private int mLayoutResource; // 需要加载的布局
private WeakReference<View> mInflatedViewRef; // 弱引用存储已加载的 View
private LayoutInflater mInflater; // 自定义 LayoutInflater
private OnInflateListener mInflateListener; // 加载监听器
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
a.recycle();
setVisibility(GONE); //默认隐藏
setWillNotDraw(true); // 不进行绘制
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0); //宽高设为0,不占用布局
}
@Override
public void draw(Canvas canvas) {
// ViewStub不进行任何绘制
}
@Override
protected void dispatchDraw(Canvas canvas) {
// ViewStub不进行任何绘制
}
//注意:inflate() 只能调用一次,否则 ViewStub 已经被替换,会抛出异常
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final View view = inflateViewNoAdd(parent); // 加载 layout
replaceSelfWithView(view, parent); //用新View替换ViewStub
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub 必须指定 layoutResource");
}
} else {
throw new IllegalStateException("ViewStub 必须有父 ViewGroup");
}
}
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
//加载layout
final View view = factory.inflate(mLayoutResource, parent, false);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
return view;
}
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this); // 找到当前ViewStub在父布局的位置
parent.removeViewInLayout(this); // 移除 ViewStub
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams); // 用新View替换
} else {
parent.addView(view, index);
}
}
@Override
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
//第二次调用setVisibility()时,ViewStub不再存在,对setVisibility()的调用转发到新View上了
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate(); // 变为可见时,触发 inflate()
}
}
}
ViewStub能减少 onMeasure()、onLayout()、onDraw() 调用,降低Hierarchy Viewer
分析的复杂度。注意事项:
- inflate() 只能调用一次,ViewStub 替换后不会再存在
- setVisibility(View.VISIBLE) 等效于 inflate()
- ViewStub 不支持事件监听(setOnClickListener() 无效)
ViewStub 是 UI 性能优化 的重要工具。通过 延迟加载,可以减少不必要的UI 渲染,提高页面响应速度。