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 小时前
Android framework 中间件开发(三)
android·中间件·framework·jni
androidwork6 小时前
掌握 Kotlin Android 单元测试:MockK 框架深度实践指南
android·kotlin
田一一一6 小时前
Android framework 中间件开发(二)
android·中间件·framework
追随远方6 小时前
FFmpeg在Android开发中的核心价值是什么?
android·ffmpeg
神探阿航7 小时前
HNUST湖南科技大学-安卓Android期中复习
android·安卓·hnust
千里马-horse9 小时前
android vlc播放rtsp
android·media·rtsp·mediaplayer·vlc
難釋懷9 小时前
Android开发-文本输入
android·gitee
志存高远6611 小时前
(面试)Android各版本新特性
android
IT从业者张某某11 小时前
信奥赛-刷题笔记-队列篇-T3-P3662Why Did the Cow Cross the Road II S
android·笔记
未来之窗软件服务12 小时前
Cacti 未经身份验证SQL注入漏洞
android·数据库·sql·服务器安全