布局优化之ViewStub源码分析

源码分析

 @RemoteView
public final class ViewStub extends View {
private int mInflatedId;
private int mLayoutResource; private WeakReference<View> mInflatedViewRef; private LayoutInflater mInflater;
private OnInflateListener mInflateListener; public ViewStub(Context context) {
this(context, );
} /**
* 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, );
} public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, );
} 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, );
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
} @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 != ) {
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);
} /** @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本身不会占用层级,它最终会被它指定的层级取代。 
在一些场合取代android:visibility=”gone”的用法,因为被gone掉的布局不断是会同时创建对象的。那为什么使用ViewStub就高效呢, 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
setMeasuredDimension(0, 0); 
}

@Override 
public void draw(Canvas canvas) { 

由onMeasure()方法和draw()方法可以看出, ViewStub的初始宽高都是零,所以他开始不会占用空间,其次draw()方法也没有执行任何的绘制,由这两个方法就可以看出,ViewStub的确很高效。 
在代码中要操纵ViewStub的时候,要首先使用viewstub.inflate()方法,将其所拥有的View初始化进去。否则会报空指针错误。 
但ViewStub也不是万能的,下面总结下ViewStub能做的事儿和什么时候该用ViewStub,什么时候该用可见性的控制。

首先来说说ViewStub的一些特点:

. ViewStub只能Inflate一次,之后ViewStub对象会被置为空。按句话说,某个被ViewStub指定的布局被Inflate后,就不会够再通过ViewStub来控制它了。
. ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。 基于以上的特点,那么可以考虑使用ViewStub的情况有:
. 在程序的运行期间,某个布局在Inflate后,就不会有变化,除非重新启动。
因为ViewStub只能Inflate一次,之后会被置空,所以无法指望后面接着使用ViewStub来控制布局。所以当需要在运行时不止一次的显示和隐藏某个布局,那么ViewStub是做不到的。这时就只能使用View的可见性来控制了。
. 想要控制显示与隐藏的是一个布局文件,而非某个View。
因为设置给ViewStub的只能是某个布局文件的Id,所以无法让它来控制某个View。
所以,如果想要控制某个View(如Button或TextView)的显示与隐藏,或者想要在运行时不断的显示与隐藏某个布局或View,只能使用View的可见性来控制。
上一篇:java常用类与包装类--常用类字符串String类、StringBuffer类、Stringbuilder类


下一篇:H5案例分享:移动端touch事件判断滑屏手势的方向