本文主要從如下幾點(diǎn)來(lái)學(xué)習(xí)ViewStub
- ViewStub是啥
- ViewStub的屬性解析
- ViewStub的代碼實(shí)操
- ViewStub的原理解析
- ViewStub實(shí)際中一般常用的情景
- ViewStub的兩個(gè)小問(wèn)題
ViewStub是啥
在介紹ViewStub是啥之前禽作,我們了解下為什么要用ViewStub
在我們?nèi)粘i_(kāi)發(fā)中,有些布局或者控件一開(kāi)始并不需要顯示群嗤,是根據(jù)業(yè)務(wù)場(chǎng)景來(lái)控制顯示狀態(tài)的总滩,我們通常的做法就是在xml文件設(shè)置不可見(jiàn)蒸甜,然后通過(guò)setVisibility()方法來(lái)更新它的可見(jiàn)性骂铁,但是這樣做會(huì)對(duì)程序的性能有一定的影響,我們知道加載布局的時(shí)候有兩個(gè)瓶頸 一個(gè)是將xml文件加載到內(nèi)存中是IO操作漫雕,通過(guò)反射或者到View的對(duì)象反射操作是耗時(shí)操作滨嘱,這兩者都是耗時(shí)的操作。
基于上面的業(yè)務(wù)情況浸间,出現(xiàn)了ViewStub的標(biāo)簽太雨,它是按需加載View,能夠很容易實(shí)現(xiàn)布局的懶加載來(lái)提升程序的性能发框。
ViewStub 繼承于 View
-
看下源碼聲明
/* * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate * layout resources at runtime. * When a ViewStub is made visible, or when {@link #inflate()} is invoked, the layout resource * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views. * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or * {@link #inflate()} is invoked. */ //ViewStub是一個(gè)不可見(jiàn)的躺彬,寬高為0的View,可用于在程序運(yùn)行的時(shí)候延遲 加載布局資源的(用于實(shí)現(xiàn)布局資源的“懶加載”) //當(dāng)使ViewStub可見(jiàn)或者調(diào)用inflate方法梅惯,可以使布局資源被加載宪拥! //ViewStub存在于視圖的層級(jí)中直到setVisibility()方法或者inflate()方法被執(zhí)行后,ViewStub相關(guān)的資源就會(huì)被加載并在控件層級(jí)結(jié)構(gòu)中代替ViewStub铣减,同時(shí)ViewStub會(huì)從控件中移除她君。
ViewStub的屬性解析
android:id
- ViewStub在布局文件中ID,用于在代碼中訪問(wèn)
- View共有的
android:layout
- 在顯示ViewStub時(shí)真正加載并且顯示的布局文件
- ViewStub特有的
android:inflatedId
- 真正布局加載后的布局Id
- ViewStub特有的
ViewStub的代碼實(shí)操
Xml資源文件
-
代碼如下
<ViewStub android:id="@+id/stub" android:inflatedId="@+id/subTree" android:layout="@layout/mySubTree android:layout_width="120dp" android:layout_height="40dp"/>
Java代碼
-
代碼如下
//1,通過(guò)id找到ViewStub葫哗,得到ViewStub對(duì)象 ViewStub myViewStub = (ViewStub)findViewById(R.id.stub); if(myViewStub!=null){ //2,通過(guò)inflate方法加載真正的布局View View myInflatedView = myViewStub.inflate(); //3,找到相應(yīng)的控件 TextView myTextView = myInflatedView.findViewById(R.id.my_text_view); }
ViewStub的原理解析
ViewStub的構(gòu)造方法
-
代碼如下
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr, defStyleRes); //獲取在xml文件中定義的inflatedId屬性 mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); //獲取到xml文件中定義的layout屬性 mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); //獲取xml文件中定義的id屬性 mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); //設(shè)置ViewStub直接不顯示 //也可以看出來(lái)缔刹,你在xml文件中如何控制它的顯示屬性,都是不顯示的 setVisibility(GONE); // 設(shè)置ViewStub不盡興繪制 setWillNotDraw(true); }
ViewStub的onMeasure和onDraw方法
-
代碼如下
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //設(shè)置寬和高都為0 也就是控件的大小為0 setMeasuredDimension(0, 0); } @Override public void draw(Canvas canvas) { //不進(jìn)行任何繪制 } @Override protected void dispatchDraw(Canvas canvas) { }
ViewStub的inflate方法
-
代碼如下
public View inflate() { //獲取ViewStub在布局文件中的父布局 final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { //mLayoutResource 就是屬性 layout指定的真正要加載的布局 if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; //把真正要顯示的View布局文件渲染成View對(duì)象并且給返回 final View view = inflateViewNoAdd(parent); //將ViewStub從布局文件結(jié)構(gòu)中移除劣针,并且把渲染好的View添加到ViewStub所處的位置校镐。 replaceSelfWithView(view, parent); mInflatedViewRef = new WeakReference<>(view); if (mInflateListener != null) { //保存當(dāng)前View對(duì)象的弱引用,方便其他地方使用 mInflateListener.onInflate(this, view); } //返回創(chuàng)建的View對(duì)象 return view; } else { //當(dāng)我們沒(méi)有為ViewStub指定layut屬性時(shí)捺典,會(huì)走這個(gè)case鸟廓,拋出異常 throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } else { //第一個(gè)調(diào)用ViewStub的inflate方法后,會(huì)把ViewStub從布局文件結(jié)構(gòu)中移除,就是沒(méi)有了ViewGroup了 // 當(dāng)?shù)诙握{(diào)用ViewStub的inflate方法后引谜,會(huì)走這個(gè)case牍陌,拋出異常。 throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); } } private View inflateViewNoAdd(ViewGroup parent) { // 獲取到布局的填充器 final LayoutInflater factory; if (mInflater != null) { factory = mInflater; } else { factory = LayoutInflater.from(mContext); } // 把真正要顯示的布局文件渲染成View對(duì)象 final View view = factory.inflate(mLayoutResource, parent, false); // mInflatedId 對(duì)應(yīng) android:inflatedId 如果指定了就為渲染好的View給設(shè)置進(jìn)去 if (mInflatedId != NO_ID) { view.setId(mInflatedId); } return view; } private void replaceSelfWithView(View view, ViewGroup parent) { // 獲取ViewStub在父布局中所處在的位置 final int index = parent.indexOfChild(this); // 將ViewStub從父布局中移除 parent.removeViewInLayout(this); // 獲取ViewStub的布局參數(shù) final ViewGroup.LayoutParams layoutParams = getLayoutParams(); // 當(dāng)設(shè)置了布局參數(shù)(例如 android:width="50dp",height="50dp") if (layoutParams != null) { // 將渲染好的View連同ViewStub的布局參數(shù)添加到ViewStub所處的位置 parent.addView(view, index, layoutParams); } else { //將渲染好的View添加到ViewStub所處的位置 parent.addView(view, index); } }
在我看來(lái)inflate方法主要的就是inflateViewNoAdd和replaceSelfWithView方法
inflateViewNoAdd:獲取到布局渲染器將真正需要展示的布局文件渲染成View并且給返回员咽。
replaceSelfWithView:將ViewStub從布局文件結(jié)構(gòu)中移除毒涧,同時(shí)把渲染好的View添加到ViewStub之前所處的位置
之后把渲染好的View的弱引用給存儲(chǔ)起來(lái)。方便在setVisibility()方法中使用贝室。
ViewStub的setVisibility()方法
-
代碼如下
public void setVisibility(int visibility) { // 縱觀全局契讲,mInflatedViewRef只有在inflate方法中初始化了, // 當(dāng)真正的布局文件被加載之后 if (mInflatedViewRef != null) { // 獲取到當(dāng)前的View View view = mInflatedViewRef.get(); if (view != null) { //操縱當(dāng)前View的可見(jiàn)行 view.setVisibility(visibility); } else { throw new IllegalStateException("setVisibility called on un-referenced view"); } } else { //沒(méi)有調(diào)用inflate的話档玻,會(huì)設(shè)置可見(jiàn)性 super.setVisibility(visibility); //當(dāng) 當(dāng)前設(shè)置可見(jiàn)性為 VISIBLE或者INVISIBLE的時(shí)候怀泊,會(huì)調(diào)用inflate方法。 if (visibility == VISIBLE || visibility == INVISIBLE) { inflate(); } } }
ViewStub實(shí)際中一般常用情景
- 比如我們?cè)跓o(wú)數(shù)據(jù)或者網(wǎng)絡(luò)錯(cuò)粗的時(shí)候误趴,需要單獨(dú)顯示一個(gè)布局,那么這個(gè)布局就可以用ViewStub务傲。
ViewStub的幾個(gè)問(wèn)題
兩次調(diào)用ViewStub的inflate方法會(huì)怎么樣凉当?
- 我們知道第一次調(diào)用inflate方法的時(shí)候,會(huì)將ViewStub從布局文件結(jié)構(gòu)中移除售葡。
- 當(dāng)?shù)诙握{(diào)用的時(shí)候看杭,ViewStub已經(jīng)沒(méi)有父控件了,當(dāng)做錯(cuò)誤檢查的時(shí)候挟伙,會(huì)拋出異常楼雹。
在xml文件中為ViewStub設(shè)置了 android:visibility="visible" 屬性,ViewStub中真正要顯示的View會(huì)顯示嘛尖阔?
- 不會(huì)贮缅,我們?cè)赩iewStub的構(gòu)造方法中,有看到它默認(rèn)調(diào)用了setVisibility(GONE)
- 所以你在xml文件中如何操作可見(jiàn)行介却,都是沒(méi)法顯示的谴供。