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

相关推荐
肖叶海几秒前
Android实现倒计时的几种方案
android
louisgeek1 小时前
Android NSD 网络服务发现
android
张可2 小时前
历时两年半开发,Fread 项目现在决定开源,基于 Kotlin Multiplatform 和 Compose Multiplatform 实现
android·前端·kotlin
余辉zmh2 小时前
【Linux系统篇】:信号的生命周期---从触发到保存与捕捉的底层逻辑
android·java·linux
孤鸿玉3 小时前
[Flutter小试牛刀] 低配版signals,添加多层监听链
android·前端·响应式设计
雨和卡布奇诺3 小时前
LiveData源码浅析
android
淡蓝色_justin3 小时前
Hilt-plus 简介
android·android jetpack
app1e2343 小时前
ctfshow web入门 命令执行(29-77)
android·前端
恋猫de小郭6 小时前
Flutter 在 Dart 3.8 开始支持 Null-Aware Elements 语法,自动识别集合里的空元素
android·前端·flutter