Android | ViewStub原理解析

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 渲染,提高页面响应速度。

相关推荐
人生苦短,菜的抠脚1 分钟前
RK Camera HAL3 工作流程简要分析
android·驱动开发
Digitally16 分钟前
5 种实测方法,在电脑上管理三星 Galaxy 应用
android
七宝大爷34 分钟前
第一个CUDA程序:从向量加法开始
android·java·开发语言
2501_9371892310 小时前
2025 优化版神马影视 8.8 源码系统|零基础部署
android·源码·开源软件·源代码管理·机顶盒
モンキー・D・小菜鸡儿13 小时前
Android Jetpack Compose 基础控件介绍
android·kotlin·android jetpack·compose
无风之翼13 小时前
android15 休眠唤醒过程中有时候屏幕显示时间一闪而过
android·锁屏
方白羽15 小时前
Android全局悬浮拖拽视图
android·app·客户端
Jerry16 小时前
Compose 高级状态和附带效应
android
2501_9160074717 小时前
苹果手机iOS应用管理全指南与隐藏功能详解
android·ios·智能手机·小程序·uni-app·iphone·webview
LFly_ice18 小时前
Nest-管道
android·java·数据库