源码基于安卓8.0分析结果
ViewStub是一种不可见的并且大小为0的试图,它可以延迟到运行时才填充inflate 布局资源,当Viewstub设为可见或者是inflate的时候,就会填充布局资源,这个布局和普通的试图就基本上没有任何区别,比如说,加载网络失败,或者是一个比较消耗性能的功能,需要用户去点击才可以加载!从而这样更加的节约了性能。对安卓布局很友好!
ViewStub用法
android:padding="10dp"
android:background="@color/colorPrimary"
android:layout_gravity="center"
android:inflatedId="@+id/view_stub_inflateid"
android:id="@+id/view_stub"
android:layout="@layout/view_stub_imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
这篇文章安卓代码、图片、布局、网络和电量优化说如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点 -----2018.6.7修正这个说法,以前我说的是错误的,根本上的原因是ViewStub设置了 inflateid ,这才是更本身的原因
在这里记住一点,如果在 ViewStub标签中设置了android:inflatedId="@+id/view_stub_inflateid",在layout布局中的根布局在设置android:id="@+id/view_stub_layout",这个id永远找出来都是为null的,原因会在下面说明
android:padding="10dp"
android:id="@+id/view_stub_layout"
android:src="@drawable/ic_launcher_background"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:text="如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点 -----2018.6.7修正这个说法,以前我说的是错误的,根本上的原因是ViewStub设置了 inflateid ,这才是更本身的原因"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_marginTop="20dp"
android:id="@+id/imageview"
android:padding="10dp"
android:src="@drawable/ic_launcher_background"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
在activity或者是fragment中的使用,mViewStub.getParent()==null就是说明没有被填充,需要填充,如果填充了,那么它的parent不会为null,具体的骚操作,后续我介绍View的绘制流程的时候在详细说明。
第一种使用的方法
mViewStub = findViewById(R.id.view_stub);
if (null!=mViewStub.getParent()){
Viewinflate = mViewStub.inflate();
....
}
第二种方式:mViewStub.setVisibility(View.VISIBLE);和inflate()方法一样。
mViewStub = findViewById(R.id.view_stub);
if (null!=mViewStub.getParent()){
mViewStub.setVisibility(View.VISIBLE);
....
}
第三种方式,my_title_parent_id是layout的根布局的id
mViewStub = findViewById(R.id.view_stub);
// 成员变量commLv2为空则代表未加载 commLv2 的id为ViewStub中的根布局的id
ViewcommLv2=findViewById(R.id.my_title_parent_id);
if ( commLv2 == null) {
// 加载评论列表布局, 并且获取评论ListView,inflate函数直接返回ListView对象
commLv2 = (View)mViewStub.inflate();
} else{
// ViewStub已经加载
}
ViewStub构造方法,注意获取了几个值mInflatedId就是android:inflatedId="@+id/find_view_stub"这个值, mLayoutResource就是layout的resId,ViewStub的 mIDid。可以看出ViewStub是View的子类.
publicfinal class ViewStub extendsView{
publicViewStub(Context context, AttributeSet attrs,intdefStyleAttr,intdefStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
// TODO: 2018/5/23 ViewStub 中设置的标签id 如果设置了 这里就一定有值 mInflatedId!=NO_Id
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);
}
}
在构造方法中:同时注意不可见 setVisibility(GONE); ,设置不绘制setWillNotDraw(true);,同时通过下面的方法看出,ViewStub 是一个大小为0的视图。
@Override
protected void onMeasure(intwidthMeasureSpec,intheightMeasureSpec) {
// 宽高都为0 onMeasure的时候 宽高都为0
setMeasuredDimension(0, 0);
}
//todo 为啥这个控件 是个大小为0的控件 ,那是因为他妈的这里更不就没有画
@Override
publicvoid draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
关于inflate()方法
publicViewinflate() {
// 1、获取ViewStub的parent view,也是目标布局根元素的parentview
final ViewParent viewParent = getParent();
if (viewParent != null&& viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
/// 2、加载目标布局 牛逼的方法
final Viewview= inflateViewNoAdd(parent);
// 3、将ViewStub自身从parent中移除
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
returnview;
} else{
// TODO: 2018/5/23 必须设置布局的文件
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else{
// TODO: 2018/5/23 iewParent instanceof ViewGroup 不属于的话,就好比在一个TextView创建一个ViewStub直接爆炸
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
第一点,ViewStup也只能在ViewGroup中使用,不能在View中去使用,要不然会抛出异常IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
第二点,也必须设置layout属性,要不然也会抛出异常throw new IllegalArgumentException("ViewStub must have a valid layoutResource");;
关于方法inflateViewNoAdd(parent);
privateViewinflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else{
factory = LayoutInflater.from(mContext);
}
final Viewview= factory.inflate(mLayoutResource, parent,false);
//和 LayoutInflater一个道理,设置了,ViewStub 引用进来的根布局的id找出来为null非常有些意思
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
returnview;
}
第一点,底层调用的还是LayoutInflater.from(mContext).inflate(mLayoutResource, parent, false);
第二点,又看到这个方法,似曾相识,对,这也是为什么ViewStub找不到根布局id的原因,因为mInflatedId != NO_ID,就会view.setId(mInflatedId);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
将ViewStub自身从parent中移除replaceSelfWithView(view, parent);,具体的原因,这里不做分析,因为有点小复杂,这里就大概明白就行,对于理解这个ViewStub不困难,哈哈
private void replaceSelfWithView(Viewview, ViewGroup parent) {
final intindex= parent.indexOfChild(this);
// 3、将ViewStub自身从parent中移除
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
// 4、将目标布局的根元素添加到parent中,有参数
parent.addView(view,index, layoutParams);
} else{
// 5、将目标布局的根元素添加到parent中
parent.addView(view,index);
}
}
这里使用到了弱引用,只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有具有弱引用的对象的时候,不论当前空间是否不足,都会对弱引用对象进行回收,当然弱引用也可以和一个队列配合着使用,为了更好地释放内存,安卓代码、图片、布局、网络和电量优化这篇文章有很好的解释,而且这个mInflatedViewRef只在这里初始化,如果说没有调用inflate的方法的话,这个对象一定为null;
//更好的释放内存
private WeakReference mInflatedViewRef;
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view)
}
为啥setVisibility(View.VISIBLE)等同于inflate,原因是ViewStub进行了重写。可以看出代码的逻辑,只要没有调用过,inflate()方法,setVisibility(VISIBLE )和setVisibility(INVISIBLE)这个两个参数走的方法一样,只不过,一个看不到,实际上的位置已经确定了(INVISIBLE)。但是如果调用多次的话setVisibility()记得也得判断下null!=mViewStub.getParent()
@Override
@android.view.RemotableViewMethod(asyncImpl ="setVisibilityAsync")
publicvoid setVisibility(intvisibility) {
// TODO: 2018/5/23 弱引用的使用
//如果已经加载过则只设置Visibility属性
if (mInflatedViewRef != null) {
Viewview= 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();// 调用inflate来加载目标布局
}
}
}
贴出全部的代码,有空的话,可以研究下。
@RemoteView
publicfinal class ViewStub extendsView{
private intmInflatedId;
private intmLayoutResource;
// TODO: 2018/5/23 弱引用:弱引用是比软引用更弱的一种的引用的类型,
// 只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有具有弱引用的对象的时候,
// 不敢当前空间是否不足,都会对弱引用对象进行回收,当然弱引用也可以和一个队列配合着使用
//更好的释放内存
private WeakReference mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
publicViewStub(Context context) {
this(context, 0);
}
/**
* Creates a new ViewStub withthe specified layout resource.
*
* @param context The application's environment.
* @param layoutResource The reference toa layout resource that will be inflated.
*/
publicViewStub(Context context, @LayoutResintlayoutResource) {
this(context, null);
mLayoutResource = layoutResource;
}
publicViewStub(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
publicViewStub(Context context, AttributeSet attrs,intdefStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
publicViewStub(Context context, AttributeSet attrs,intdefStyleAttr,intdefStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
// TODO: 2018/5/23 ViewStub 中设置的标签id 如果设置了 这里就一定有值 mInflatedId!=NO_Id
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);
}
/**
* Returnsthe id takenbythe inflatedview. If the inflated idis
* {@link View#NO_ID}, the inflatedviewkeeps its original id.
*
* @returnA positiveintegerusedtoidentify the inflatedviewor
* {@link #NO_ID} if the inflated viewshould keep its id.
*
* @see #setInflatedId(int)
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
@IdRes
publicintgetInflatedId() {
returnmInflatedId;
}
/**
* Defines the id taken bythe inflatedview. If the inflated idis
* {@link View#NO_ID}, the inflatedviewkeeps its original id.
*
* @param inflatedId A positive integerusedtoidentify the inflatedviewor
* {@link #NO_ID} if the inflated viewshould keep its id.
*
* @see #getInflatedId()
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
@android.view.RemotableViewMethod(asyncImpl ="setInflatedIdAsync")
publicvoid setInflatedId(@IdResintinflatedId) {
mInflatedId = inflatedId;
}
/** @hide **/
publicRunnable setInflatedIdAsync(@IdResintinflatedId) {
mInflatedId = inflatedId;
returnnull;
}
/**
* Returnsthe layout resource that will be usedby{@link #setVisibility(int)}or
* {@link #inflate()} toreplacethis StubbedView
* inits parentbyanotherview.
*
* @returnThe layout resource identifier usedtoinflate the newView.
*
* @see #setLayoutResource(int)
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
@LayoutRes
publicintgetLayoutResource() {
returnmLayoutResource;
}
/**
* Specifies the layout resource toinflatewhenthis StubbedView becomes visibleorinvisible
* orwhen{@link #inflate()}isinvoked. TheViewcreatedbyinflating the layout resourceis
* used toreplacethis StubbedViewinits parent.
*
* @param layoutResource A valid layout resource identifier (different from0.)
*
* @see #getLayoutResource()
* @see #setVisibility(int)
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
@android.view.RemotableViewMethod(asyncImpl ="setLayoutResourceAsync")
publicvoid setLayoutResource(@LayoutResintlayoutResource) {
mLayoutResource = layoutResource;
}
/** @hide **/
publicRunnable setLayoutResourceAsync(@LayoutResintlayoutResource) {
mLayoutResource = layoutResource;
returnnull;
}
/**
* Set{@link LayoutInflater}tousein{@link #inflate()},or{@codenull}
* touse thedefault.
*/
publicvoid setLayoutInflater(LayoutInflater inflater) {
mInflater = inflater;
}
/**
* Get current{@link LayoutInflater} usedin{@link #inflate()}.
*/
publicLayoutInflater getLayoutInflater() {
returnmInflater;
}
@Override
protected void onMeasure(intwidthMeasureSpec,intheightMeasureSpec) {
// 宽高都为0 onMeasure的时候 宽高都为0
setMeasuredDimension(0, 0);
}
//todo 为啥这个控件 是个大小为0的控件 ,那是因为他妈的这里更不就没有画
@Override
publicvoid draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
/**
* Whenvisibilityissetto{@link #VISIBLE}or{@link #INVISIBLE},
* {@link #inflate()} isinvokedandthis StubbedViewisreplacedinits parent
* bythe inflated layout resource.Afterthat callstothisfunctionare passed
* through tothe inflatedview.
*
* @param visibility One of{@link #VISIBLE}, {@link #INVISIBLE},or{@link #GONE}.
*
* @see #inflate()
*/
@Override
@android.view.RemotableViewMethod(asyncImpl ="setVisibilityAsync")
publicvoid setVisibility(intvisibility) {
// TODO: 2018/5/23 弱引用的使用
//如果已经加载过则只设置Visibility属性
if (mInflatedViewRef != null) {
Viewview= 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();// 调用inflate来加载目标布局
}
}
}
/** @hide **/
publicRunnable setVisibilityAsync(intvisibility) {
if (visibility == VISIBLE || visibility == INVISIBLE) {
ViewGroup parent = (ViewGroup) getParent();
returnnew ViewReplaceRunnable(inflateViewNoAdd(parent));
} else{
returnnull;
}
}
private ViewinflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else{
factory = LayoutInflater.from(mContext);
}
final Viewview= factory.inflate(mLayoutResource, parent,false);
//和 LayoutInflater一个道理,设置了,ViewStub 引用进来的根布局的id找出来为null非常有些意思
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
returnview;
}
// TODO: 2018/5/23 关注他
private void replaceSelfWithView(Viewview, ViewGroup parent) {
final intindex= parent.indexOfChild(this);
// 3、将ViewStub自身从parent中移除
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
// 4、将目标布局的根元素添加到parent中,有参数
parent.addView(view,index, layoutParams);
} else{
// 5、将目标布局的根元素添加到parent中
parent.addView(view,index);
}
}
/**
* Inflates the layout resource identified by{@link #getLayoutResource()}
* andreplaces this StubbedViewinits parentbythe inflated layout resource.
*
* @returnThe inflated layout resource.
*
*/
publicViewinflate() {
// 1、获取ViewStub的parent view,也是目标布局根元素的parentview
final ViewParent viewParent = getParent();
if (viewParent != null&& viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
/// 2、加载目标布局 牛逼的方法
final Viewview= inflateViewNoAdd(parent);
// 3、将ViewStub自身从parent中移除
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
returnview;
} else{
// TODO: 2018/5/23 必须设置布局的文件
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else{
// TODO: 2018/5/23 iewParent instanceof ViewGroup 不属于的话,就好比在一个TextView创建一个ViewStub直接爆炸
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
/**
* Specifies the inflate listener tobe notifiedafterthis ViewStub successfully
* inflated its layout resource.
*
* @param inflateListener The OnInflateListener tonotifyofsuccessful inflation.
*
* @see ViewStub.OnInflateListener
*/
publicvoid setOnInflateListener(OnInflateListener inflateListener) {
mInflateListener = inflateListener;
}
/**
* Listener used toreceive a notificationaftera ViewStub has successfully
* inflated its layout resource.
*
* @see ViewStub#setOnInflateListener(ViewStub.OnInflateListener)
*/
publicstaticinterface OnInflateListener {
/**
* Invoked aftera ViewStub successfully inflated its layout resource.
* This method isinvokedafterthe inflatedviewwas addedtothe
* hierarchy but before the layout pass.
*
* @param stub The ViewStub that initiated the inflation.
* @param inflated The inflated View.
*/
void onInflate(ViewStub stub, Viewinflated);
}
/** @hide **/
publicclass ViewReplaceRunnable implements Runnable {
publicfinalViewview;
ViewReplaceRunnable(Viewview) {
this.view=view;
}
@Override
publicvoid run() {
replaceSelfWithView(view, (ViewGroup) getParent());
}
}
}
最后做了一张图
说明一下ViewStub的原理很简单!好吧,这个有点皮
【编辑推荐】
【责任编辑:未丽燕 TEL:(010)68476606】
点赞 0