android view 源码分析,基于Android 8.0分析源码的ViewStub源码解析

源码基于安卓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());

}

}

}

最后做了一张图

154e404e28f1d35b9464c0d96c0f8a5b.png

说明一下ViewStub的原理很简单!好吧,这个有点皮

【编辑推荐】

【责任编辑:未丽燕 TEL:(010)68476606】

点赞 0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值