前言
ViewStub控件,惰性装载控件。在介绍ViewStub之前,可以先了解一下
示例
text_layout就是需要惰性加载的布局,
如何使用?
//MainActivity .java
public class MainActivity extends AppCompatActivity {
ViewStub viewStub;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewStub = findViewById(R.id.viewStud);
viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
Log.i(TAG, "onInflate: ");
TextView textView = inflated.findViewById(R.id.tv_show);
textView.setText("chang text content");
}
});
viewStub.inflate();
}
}
使用方法的代码很简单,就是让viewstub做一个站位的操作,将需要懒加载的布局传递给viewstub控件(通过viewstub的layout属性),在代码中找到viewstub实例,可以给它设置一个inflate的监听,在真正调用inflate()方法或者设置可见的时候会监听到对应的懒加载view。
源码分析
(PS:ViewStub.java文件总共也就300多行,这可能是最好分析的Android SDK源码了吧!既然这样,代码全贴出来也不过分 ^.^ )
public final class ViewStub extends View {
private int mInflatedId;
private int mLayoutResource;
private WeakReference mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
public ViewStub(Context context) {
this(context, 0);
}
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);
}
@IdRes
public int getInflatedId() {
return mInflatedId;
}
@android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
public void setInflatedId(@IdRes int inflatedId) {
mInflatedId = inflatedId;
}
/** @hide **/
public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
mInflatedId = inflatedId;
return null;
}
@LayoutRes
public int getLayoutResource() {
return mLayoutResource;
}
@android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
public void setLayoutResource(@LayoutRes int layoutResource) {
mLayoutResource = layoutResource;
}
/** @hide **/
public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
mLayoutResource = layoutResource;
return null;
}
public void setLayoutInflater(LayoutInflater inflater) {
mInflater = inflater;
}
/**
* Get current {@link LayoutInflater} used in {@link #inflate()}.
*/
public LayoutInflater getLayoutInflater() {
return mInflater;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
@Override
@android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
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();
}
}
}
/** @hide **/
public Runnable setVisibilityAsync(int visibility) {
if (visibility == VISIBLE || visibility == INVISIBLE) {
ViewGroup parent = (ViewGroup) getParent();
return new ViewReplaceRunnable(inflateViewNoAdd(parent));
} else {
return null;
}
}
private View inflateViewNoAdd(ViewGroup parent) {
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);
}
return view;
}
private void replaceSelfWithView(View view, ViewGroup parent) {
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);
}
}
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);
replaceSelfWithView(view, parent);
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");
}
}
public void setOnInflateListener(OnInflateListener inflateListener) {
mInflateListener = inflateListener;
}
public static interface OnInflateListener {
void onInflate(ViewStub stub, View inflated);
}
/** @hide **/
public class ViewReplaceRunnable implements Runnable {
public final View view;
ViewReplaceRunnable(View view) {
this.view = view;
}
@Override
public void run() {
replaceSelfWithView(view, (ViewGroup) getParent());
}
}
}
ViewStub也是继承自View,所以可以成为xml布局中的一个站位控件,除了ViewStub外,里面还包含了一个接口OnInflateListener 和一个ViewReplaceRunnable类,OnInflateListener 用于监听ViewStub加载布局时的inflate监听。ViewReplaceRunnable是一个异步加载布局的Runnable.在调用setVisibilityAsync()方法时会返回runnable对象。用于异步加载控件。
mInflatedId:需要加载的布局id,
mLayoutResource:需要加载的布局
mInflatedViewRef:对需要加载的布局view的一个弱引用的持有
我们主要关注一下ViewStub的三个方法
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
onMeasure方法,只有一句代码setMeasuredDimension(0, 0);
所以说,不管ViewStub设置多大,在初始化时都是一个没有大小的(A ViewStub is an invisible, zero-sized View)。至于不可见,可以在初始化里面看到,默认是设置了setVisibility(GONE);
setVisibility(int visibility)
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()方法中去
inflate()
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);
replaceSelfWithView(view, parent);
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");
}
}
在inflate方法中首先会调用inflateViewNoAdd方法
private View inflateViewNoAdd(ViewGroup parent) {
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);
}
return view;
}
inflateViewNoAdd方法就是通过factory加载设置的视图mLayoutResource,并返回view对象。然后会调用replaceSelfWithView(view, parent)方法;
private void replaceSelfWithView(View view, ViewGroup parent) {
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);
}
}
将inflateViewNoAdd方法返回的view对象加入到ViewStub的父view上,完成ViewStub占位的替换。最后
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
将加载好的view放在弱引用中持有,用于后面的显示隐藏对view的重新操作,同时回调onInflate。
其中还有一个重要的setVisibilityAsync(int visibility)方法,同样会通过LayoutInflater把mLayoutResource转换成view对象,此方法和setVisibility(int visibility)唯一的区别就时,在于它会返回一个Runnable对象,然后可以在想要加载的时候将view加载到界面中。
以上,分析完毕!如有错误,欢迎指出~