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

相关推荐
铁手飞鹰18 分钟前
Visual Studio创建Cmake工程导出DLL,通过Python调用DLL
android·python·visual studio
冰语竹5 小时前
Android学习之相对布局
android
没有了遇见5 小时前
Android 中大型项目架构梳理
android
yashuk5 小时前
【MySQL】表的相关操作
android·mysql·adb
71-35 小时前
Android studio中真机操作
android·笔记·学习·其他·android studio
一只特立独行的Yang6 小时前
Android Focus小结
android
aaajj10 小时前
【Android】appops学习
android·学习
煤球王子10 小时前
学习记录:Android14中的Wifi_Direct(P2P)
android
找藉口是失败者的习惯10 小时前
【Android】Android 车机 + AI Agent 有没有搞头?
android·人工智能
用户0044521593011 小时前
从 Gradle 到 Transform:Android 编译开发 Part 1 - Gradle 构建初探
android