1. 特性
- ViewStub 是一个不可见,size为0的View,它通常用于在适当的时机去懒加载布局。
- 一旦 ViewStub 设置为 Visible 或者 调用了 inflate() 方法,ViewStub 的布局就会被加载。
- ViewStub 的布局在加载后会直接替换它自己,所以 ViewStub 存在于 View hierarchy 直到它调用了 setVisiblity() 或者 inflate()。
- ViewStub 设置的参数会直接传递给自身的布局。
2. 示例
<ViewStub
android:id="@+id/view_stub"
android:layout_margin="10dp"
android:inflatedId="@+id/tv_title"
android:layout="@layout/tv_title_layout"
android:layout_width="220dp"
android:layout_height="50dp" />
- 如上面,我们可以使用 id/view_stub 去寻找到 ViewStb, ViewStub在加载布局后,ViewStub 本身消失了,其 android:inflatedId="@+id/tv_title" 指定的就是布局的ID,通过这个ID就可以找到加载后的布局。
- 最后这个布局的大小就是继承于 ViewStub 的 120dp 40dp。
- 还有,android:inflatedId="@+id/tv_title" 这里指定的ID必须要和 android:layout="@layout/tv_title_layout" 里面的ID对应起来。
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_title"
android:text="I'm inflate view."
android:textColor="#000"
android:textSize="15sp"
android:layout_width="match_parent"
android:layout_height="match_parent">
</TextView>
虽然,layout里面布局写的 width 和 height 为 match_parent,但是不生效,只会继承于 viewstub 指定的大小。
我们在代码里面就可以这样写:
ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);
if (viewStub!=null) {
viewStub.inflate();
}
TextView tvTitle = (TextView) findViewById(R.id.tv_title);
tvTitle.setText("I'm inflate: " + System.currentTimeMillis());
3. 源码分析
变量
private int mInflatedId;//viewstub传递给布局的id
private int mLayoutResource;//viewstub的布局
private WeakReference<View> mInflatedViewRef;
这里有一个弱引用,是用来干嘛的呢?我们先不管,往下看。
构造函数
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);
}
很普通,直接从xml布局中读取相应的属性,并且在构造函数中就设置为 GONE,接着:setWillNotDraw(true),设置一个标记,声明这个View不做 onDraw 绘制。
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
onMeasure,draw
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
measure直接传入0,说明ViewStub初始化就是一个0大小的View。
setVisibility & inflate
从上面知道,inflate 或者 setVisibility 都可以加载布局,我们先看:setVisibility:
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();
}
}
}
先判断弱引用里面是否有View,有的话就直接设置为可见,为空,就执行 inflate():
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
//mInflater 可以从外部传进来
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);
}
//获取当前ViewStub在ViewGroup中的index
final int index = parent.indexOfChild(this);
//ViewStub替换成inflate后的View
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>(view);
//inflate回调
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
}
//省略异常
}
//省略异常
}
- 先获取到父容器,然后判断布局不为空情况下,进行加载布局
- 使用的mInflate可以从外部通过方法 public void setLayoutInflater(LayoutInflater inflater) 进来
- 加载布局后设置好布局的ID
- 接着获取到当前 ViewStub 的位置 index,remove viewStub,add 新inflate 的 View 到 Index 位置上,也就是替换原来的 ViewStub
- 初始化弱引用,从这里看到,弱引用包裹的是加载的 View
- 回调器回调
回调
public static interface OnInflateListener {
void onInflate(ViewStub stub, View inflated);
}
在 inflate 的时候会进行回调
网友评论