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

相关推荐
tangweiguo0305198714 小时前
Flutter与原生混合开发:实现完美的暗夜模式同步方案
android·flutter
雨白15 小时前
深入理解 Android 触摸事件:以实现 ViewPager 为例
android
shenshizhong15 小时前
看懂鸿蒙系统源码 比较重要的知识点
android·harmonyos
一只修仙的猿16 小时前
再谈性能优化,一次项目优化经历分享
android·性能优化
雮尘18 小时前
Android性能优化之枚举替代
android
2501_9159090620 小时前
苹果上架App软件全流程指南:iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传与审核技巧详解
android·ios·小程序·https·uni-app·iphone·webview
2501_9159214320 小时前
iOS 文件管理与能耗调试结合实战 如何查看缓存文件、优化电池消耗、分析App使用记录(uni-app开发与性能优化必备指南)
android·ios·缓存·小程序·uni-app·iphone·webview
2501_9159184121 小时前
App 苹果 上架全流程解析 iOS 应用发布步骤、App Store 上架流程
android·ios·小程序·https·uni-app·iphone·webview
2501_9160074721 小时前
苹果上架全流程详解,iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传与审核要点完整指南
android·ios·小程序·https·uni-app·iphone·webview
PuddingSama1 天前
Android 高级绘制技巧: BlendMode
android·前端·面试