ViewStub 是一个看不见的,没有大小,不占布局位置的 View,可以用来懒加载布局。当 ViewStub 变得可见或 inflate()
的时候,布局就会被加载(替换 ViewStub)。因此,ViewStub 一直存在于视图层次结构中直到调用了 setVisibility(int)
或 inflate()
。
我们先来看看构造方法:
非常简单的两步,就能做到View的懒加载,非常方便,其原因是什么呢?
接下去深入源码分析一下。
构造方法分析
首先分析一下构造方法,了解一下它是如何创建的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public ViewStub(Context context, @LayoutRes int layoutResource) { this(context, null); mLayoutResource = layoutResource; } public ViewStub(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } 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); mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); setVisibility(GONE); setWillNotDraw(true); }
|
ViewStub
在构造方法里不仅仅获取赋值属性,比较关键的是,还 默认将ViewStub自己设置为不可见(跳过onMeasure与onLayout),不绘制。
这里有一个要点:在XML里配置ViewStub的可见性是没有用的。
测量 与 绘制
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(0, 0); } @Override public void draw(Canvas canvas) { } @Override protected void dispatchDraw(Canvas canvas) { }
|
inflate()方法分析
之前在简单教程里有提到 inflate
方法,它是ViewStub
实现懒加载的最为关键的方法,接下去去分析一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public View inflate() { final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; final LayoutInflater factory; if (mInflater != null) { factory = mInflater; } else { factory = LayoutInflater.from(mContext); } final View view = factory.inflate(mLayoutResource, parent, false); if (mInflatedId != NO_ID) { view.setId(mInflatedId); } final int index = parent.indexOfChild(this); parent.removeViewInLayout(this); final ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams != null) { parent.addView(view, index, layoutParams); } else { parent.addView(view, index); } mInflatedViewRef = new WeakReference(view); if (mInflateListener != null) { mInflateListener.onInflate(this, view); } return view; } else { throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } else { throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); } }
|
我在每行代码上都加上了详细的注释,主要的操作就是把StubbedView给Inflate出来,然后把它放到自己的位置,代码非常清晰,非常简单。
总结来说,其实inflate
方法是做了一个『偷梁换柱』的操作,把 StubbedView
动态的添加到自己原来的位置上,也因此实现了懒加载功能。
这里还需要注意的是 ViewStub 必须要有一个 Parent,即必须要有父视图!
另外值得一提的是:ViewStub还重写了View的setVisibility
方法,让我们来分析一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void setVisibility(int visibility) { if (mInflatedViewRef != null) { 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(); } } }
|
可以看到setVisibility
方法中也可能会调用inflate()
方法,所以当我们想让StubbedView被加载进来,而我们不需要StubbedView的实例的时候,可以用setVisibility(View.VISIBLE)
。
不过需要注意的是 不要再接着调用inflate
方法,因为此时的 ViewStub 已经被移除了!
要点
- 使用ViewStub,必须指定layoutResourceId(必须是布局文件)
- 在XML里配置ViewStub的可见性是没有用的
- ViewStub 主要原理藏在
inflate()
方法中,是它把真正要加载的View给加载了进来
inflate()
方法只能调用一次
- ViewStub调用
inflate()
后就不要再用它了(让它功成身退!)
- 要小心
setVisibility
方法,因为它可能会调用inflate()
- 在XML里给ViewStub设置的LayoutParamas(宽高margin等)会传递给StubbedView,所以我们如果要控制StubbedView的LayoutParamas,则需要写在ViewStub里而不是StubbedView!
- 期待补充
好了,主要的ViewStub都分析完了,知道原理之后就可以自己动手写一个加强版的 ViewStub 。