前言
关于布局优化的内容之前总结过一篇,可以移步Android布局优化总结,今天我们从源码的角度来分析一下ViewStub是如何实现的。
源码分析
@RemoteView
public final class ViewStub extends View {
private int mInflatedId; // 被填充View的id
private int mLayoutResource; // layout布局资源id
// 弱引用,引用填充的View,以便在inflate之后可以通过setVisibility方法设置可见性
private WeakReference<View> mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener; // inflate监听
public ViewStub(Context context) {
this(context, 0);
}
/**
* Creates a new ViewStub with the specified layout resource.
*
* @param context The application's environment.
* @param layoutResource The reference to a layout resource that will be inflated.
*/
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); // 如果View不绘制任何内容,设置这个标记可以优化性能,默认View没有设置这个标记,如果重写onDraw,就不要设置这个标记
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0); // 测量时尺寸为0
}
@Override
public void draw(Canvas canvas) { // 不绘制内容
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
..... 省去部分代码
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
} // 通过inflate填充布局
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); // 移除ViewStub,后面不能在inflate
final ViewGroup.LayoutParams layoutParams = getLayoutParams(); // 获得ViewStub的布局参数
if (layoutParams != null) {
parent.addView(view, index, layoutParams); // 把ViewStub指定的布局添加到parent中
} else {
parent.addView(view, index);
}
}
/**
* Inflates the layout resource identified by {@link #getLayoutResource()}
* and replaces this StubbedView in its parent by the inflated layout resource.
*
* @return The inflated layout resource.
*
*/
public View inflate() {
final ViewParent viewParent = getParent(); // 获取ViewStub的parent
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");
}
}
/**
* Specifies the inflate listener to be notified after this ViewStub successfully
* inflated its layout resource.
*
* @param inflateListener The OnInflateListener to notify of successful inflation.
*
* @see android.view.ViewStub.OnInflateListener
*/
public void setOnInflateListener(OnInflateListener inflateListener) {
mInflateListener = inflateListener;
}
/**
* Listener used to receive a notification after a ViewStub has successfully
* inflated its layout resource.
*
* @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
*/
public static interface OnInflateListener {
/**
* Invoked after a ViewStub successfully inflated its layout resource.
* This method is invoked after the inflated view was added to the
* hierarchy but before the layout pass.
*
* @param stub The ViewStub that initiated the inflation.
* @param inflated The inflated View.
*/
void onInflate(ViewStub stub, View inflated); // ViewStub填充完毕的回调
}
/** @hide **/
public class ViewReplaceRunnable implements Runnable {
public final View view;
ViewReplaceRunnable(View view) {
this.view = view;
}
@Override
public void run() {
replaceSelfWithView(view, (ViewGroup) getParent());
}
}
}