ViewStub应用的场景比较少,以前用过几次。时间一长又忘记了,很是苦恼。
于是把ViewStub的源码过了一遍,详细说明了每行代码的含义和目的,翻译了源码中的注释。
自己动眼看看吧,你懂得。再多说就画蛇添足了。
道士,请:
package android.view;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
import java.lang.ref.WeakReference;
/**
* ViewStub是一个不可见的,0 size的View,它可以在运行时加载一个布局,即懒加载。
* 当ViewStub设置为可见(visible),或者调用了ViewStub的inflate()方法时,
* 其android:layout属性所关联的layout文件才会被加载和生成view对象。
* 此时,当前ViewStub所在的位置会被其加载的子布局霸占替换;并且当前ViewStub对象会从其父容器中移除。
* 这也就是为什么ViewStub只能inflate()一次的原因。inflate一次后,ViewStub对象都没有了,还怎么inflate()第二次?
*
* 下面这句话注意了:
* 被ViewStub加载的View,替换了ViewStub的存在,当然是添加到了ViewStub的父容器中,即原ViewStub的爸爸成了ViewStub加载的View的爸爸。
* 并且在ViewStub上设置的layout参数,会转移给ViewStub加载的View身上。
* 同样的,你也可以在ViewStub的android:inflatedId属性上设置id,当其加载布局时,会直接把id重新设置到加载的布局根上。
* 如果没设置android:inflatedId属性,那待加载的布局根还是用以前的id属性,没有拉到。
*
* 举个栗子:
*
*
*
*android:inflatedId="@+id/这个id会在运行时加载中,设置到下面aaaaa布局的根属性上"
*android:layout="@layout/aaaaa,即稍后会加载且替换当前ViewStub的布局"
*android:layout_width="120dp"
*android:layout_height="40dp"/>
*
*
* 如上面的代码,当aaaaa布局加载后,你可以使用inflatedId属性值(即id值)找到aaaaa对象
* 或者用aaaaa内部的根布局id找到aaaaa对象。
*
* 下面ViewStub的代码:
*
* ViewStubstub= (ViewStub) findViewById(R.id.viewStub的id);
* Viewinflated=stub.inflate();
*
*
* 当inflate()方法被调用后,当前的ViewStub将会被加载的布局替换,并且返回值为加载的布局对象。
*
* 注意下面两个属性的定义:
* @attr ref android.R.styleable#ViewStub_inflatedId 给待加载的布局设置id,也可以不设置,用布局内部设置的id值
* @attr ref android.R.styleable#ViewStub_layout 待加载的布局
*/
@RemoteView
public final class ViewStub extends View {
private int mInflatedId;
private int mLayoutResource;
private WeakReferencemInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
public ViewStub(Context context) {
this(context, 0);
}
/**
* 使用给定的待加载的布局文件创建一个ViewStub对象。
*
* @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 TypedArraya=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();
//注意,ViewStub永远是隐藏的,不会展示任何UI信息,且不占位
setVisibility(GONE);
setWillNotDraw(true);
}
/**
* 返回待加载的布局的id,如果没有设置(即保持布局内部根属性上设置的id)则返回NO_ID
*
* @return A positive integer used to identify the inflated view or
* {@link #NO_ID} if the inflated view should keep its id.
*
* @see #setInflatedId(int)
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
@IdRes
public int getInflatedId() {
return mInflatedId;
}
/**
* Defines the id taken by the inflated view. If the inflated id is
* {@link View#NO_ID}, the inflated view keeps its original id.
*
* @param inflatedId A positive integer used to identify the inflated view or
* {@link #NO_ID} if the inflated view should keep its id.
*
* @see #getInflatedId()
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
@android.view.RemotableViewMethod
public void setInflatedId(@IdRes int inflatedId) {
mInflatedId=inflatedId;
}
/**
* 获取待加载的布局资源
*
* @return The layout resource identifier used to inflate the new View.
*
* @see #setLayoutResource(int)
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
@LayoutRes
public int getLayoutResource() {
return mLayoutResource;
}
/**
* 代码形式设置待加载的布局资源
*
* @param layoutResource A valid layout resource identifier (different from 0.)
*
* @see #getLayoutResource()
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
@android.view.RemotableViewMethod
public void setLayoutResource(@LayoutRes int layoutResource) {
mLayoutResource=layoutResource;
}
/**
* Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
* to use the default.
*/
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) {
//设置当前ViewStub的宽高都为0,即不可见了
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
//无绘制
}
@Override
protected void dispatchDraw(Canvas canvas) {
//也不调用子孩子的绘制
}
/**
* When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
* {@link #inflate()} is invoked and this StubbedView is replaced in its parent
* by the inflated layout resource. After that calls to this function are passed
* through to the inflated view.
*
* @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
*
* @see #inflate()
*/
@Override
@android.view.RemotableViewMethod
public void setVisibility(int visibility) {
//判断mInflatedViewRef对象是否为null,即判断是否调用过下面的inflate()函数
if (mInflatedViewRef != null) {
//已经加载过view,直接获取。
Viewview=mInflatedViewRef.get();
if (view != null) {
//直接设置view的可见性即可,此时跟ViewStub没什么关系了。ViewStub已经从父容器中移除了
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if (visibility== VISIBLE ||visibility== INVISIBLE) {
//如果设置了ViewStub为可见,或者占位的话,则触发下面的inflate()函数
//注意,有了mInflatedViewRef的判断,inflate()函数是不会调用第二次的。
//当调用了一次inflate()函数后,当前ViewStub对象没有父容器了,然而在调用inflate()会报异常的。
inflate();
}
}
}
/**
* 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() {
//获取到当前ViewStub对象的父容器
final ViewParentviewParent=getParent();
//判断父容器是否为null
if (viewParent != null && viewParent instanceof ViewGroup) {
//判断是否给ViewStub指定了android:layout=""属性,即待加载的布局
if (mLayoutResource != 0) {
final ViewGroupparent= (ViewGroup) viewParent;
final LayoutInflater factory;
if (mInflater != null) {
factory=mInflater;
} else {
factory=LayoutInflater.from(mContext);
}
//加载指定的资源文件,注意三个参数的含义
//可参考:http://blog.csdn.net/fesdgasdgasdg/article/details/72870280
final Viewview=factory.inflate(mLayoutResource, parent,
false);
//如果设置了android:inflatedId=""属性值,则把设置的id值赋给被加载的布局
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
//记录当前ViiewStub在父容器中的位置
final intindex=parent.indexOfChild(this);
//把当前ViewStub对象从父容器中移除
parent.removeViewInLayout(this);
//获取当前ViewStub的layoutParams参数
final ViewGroup.LayoutParamslayoutParams=getLayoutParams();
if (layoutParams != null) {
//连同ViewStub的layoutParams参数一起,把加载获得到的View设置到父容器相应的位置,代替了ViewStub的存在
//注意layoutParams参数包含哪些值
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
//创建加载布局的弱引用,在上面setVisibility()函数中会用到
mInflatedViewRef=newWeakReference(view);
if (mInflateListener != null) {
//布局加载成功,并且成功替换了ViewStub的位置,和拥有了ViewStub的资源。于是执行回调通知调用者
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");
}
}
/**
* 设置回调,当ViewStub成功的加载了布局资源后,会回调此接口进行通知
*
* @param inflateListener The OnInflateListener to notify of successful inflation.
*
* @see android.view.ViewStub.OnInflateListener
*/
public void setOnInflateListener(OnInflateListener inflateListener) {
mInflateListener=inflateListener;
}
/**
* 资源文件成功被ViewStub加载后的监听器
*
* @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);
}
}