ViewStub--使用介紹
(1)什麼時候使用ViewStub?為什麼使用ViewStub?
當我們需要根據某個條件控制某個View的顯示或者隱藏的時候,通常是把可能用到的View都寫在佈局上,然後設定可見性為View.GONE或View.InVisible ,之後在程式碼中根據條件動態控制可見性。雖然操作簡單,但是耗費資源,因為即便該view不可見,仍會被父窗體繪製,仍會建立物件,仍會被例項化,仍會被設定屬性。
而android.view.ViewStub,是一個大小為0 ,預設不可見的控制元件,只有給他設定成了View.Visible或呼叫了它的inflate()之後才會填充佈局資源,也就是說佔用資源少。所以,推薦使用viewStub
(2)ViewStub基本介紹
官方文件地址:https://developer.android.com/reference/android/view/ViewStub.html
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 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 setVisibility(int) or inflate() is invoked. The inflated View is added to the ViewStub's parent with the ViewStub's layout parameters. Similarly, you can define/override the inflate View's id by using the ViewStub's inflatedId property. For instance:
我的翻譯:
ViewStub 是一個不可見的,大小為0的檢視,可以在執行過程中延時載入佈局資源。當ViewStub被設定成可見,或者它的inflate() 方法被呼叫的時候,佈局資源才會被填充,然後ViewStub本身就會被填充起來的佈局資源替換掉。也就是說 ViewStub 被設定成可見或者它的inflate() 方法被呼叫之後,在檢視樹中就不存在了。被填充的佈局在替換ViewStub的時候會使用ViewStub的佈局引數(LayoutParameters),比如 width ,height等。此外,你也可以通過ViewStub的inflateId 屬性定義或者重寫 被填充佈局資源的id。
<ViewStub android:id="@+id/stub"
android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dip"
android:layout_height="40dip" />
官方文件原文:The ViewStub thus defined can be found using the id "stub." After inflation of the layout resource "mySubTree," the ViewStub is removed from its parent. The View created by inflating the layout resource "mySubTree" can be found using the id "subTree," specified by the inflatedId property. The inflated View is finally assigned a width of 120dip and a height of 40dip. The preferred way to perform the inflation of the layout resource is the following:
我的翻譯:
在上面的示例程式碼中,可以通過id 獲取到ViewStub,當layout屬性引用的佈局資源 mySubTree 被填充之後,ViewStub就會從它的父窗體中移除,取而代之的就是mySubTree。通過inflatedId 屬性的值可以獲取到mySubTree。mySubTree 在替代ViewSub的時候會使用ViewStub的layoutParametes, 也就是說mySubTree 的寬高會被定義成 120dp 、40dp。 推薦用如下方式去實現mySubTree佈局資源的填充:
ViewStub stub = (ViewStub) findViewById(R.id.stub);
View inflated = stub.inflate();
When inflate() is invoked, the ViewStub is replaced by the inflated View and the inflated View is returned. This lets applications get a reference to the inflated View without executing an extra findViewById().
我的翻譯:
當ViewStub 的inflate() 方法被呼叫之後,ViewStub就會被填充起來的佈局替換掉,並返回填充起來的View。這樣,當我們想使用被填充起來的View時就不再需要呼叫findViewById () 方法。(實際使用的時候,如果我們需要操作被填充佈局裡面的資料時用inflate(),否則可以直接使用setVisibility)
(3)示例程式碼:
最終效果展示:
- activity_viewstub_test02.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_vs_showView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="顯示ViewStub"/>
<Button
android:id="@+id/btn_vs_changeHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="更改ViewStub"/>
<Button
android:id="@+id/btn_vs_hideView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_weight="1"
android:text="隱藏ViewStub"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="正在載入。。。"/>
<!--ViewStub 展示或者隱藏內容-->
<ViewStub
android:id="@+id/viewstub_test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inflatedId="@+id/iv_VsContent"
android:layout="@layout/iv_vs_content"/>
</RelativeLayout>
- ViewStubTestActivitiy.java
/**
* Created by CnPeng on 2017/1/11. ViewStub 的使用示例
*/
public class ViewStubTestActivitiy extends AppCompatActivity implements View.OnClickListener {
private ViewStub viewStub;
private TextView hintText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewstub_test02);
init();
}
/**
* 初始化
*/
private void init() {
viewStub = (ViewStub) findViewById(R.id.viewstub_test);
Button btn_show = (Button) findViewById(R.id.btn_vs_showView);
Button btn_hide = (Button) findViewById(R.id.btn_vs_hideView);
Button btn_change = (Button) findViewById(R.id.btn_vs_changeHint);
btn_show.setOnClickListener(this);
btn_hide.setOnClickListener(this);
btn_change.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_vs_showView:
//inflate 方法只能被呼叫一次,因為呼叫後viewStub物件就被移除了檢視樹;
// 所以,如果此時再次點選顯示按鈕,就會崩潰,錯誤資訊:ViewStub must have a non-null ViewGroup viewParent;
// 所以使用try catch ,當此處發現exception 的時候,在catch中使用setVisibility()重新顯示
try {
View iv_vsContent = viewStub.inflate(); //inflate 方法只能被呼叫一次,
hintText = (TextView) iv_vsContent.findViewById(R.id.tv_vsContent);
// hintText.setText("沒有相關資料,請重新整理");
} catch (Exception e) {
viewStub.setVisibility(View.VISIBLE);
} finally {
hintText.setText("沒有相關資料,請重新整理");
}
break;
case R.id.btn_vs_hideView: //如果顯示
viewStub.setVisibility(View.INVISIBLE);
break;
case R.id.btn_vs_changeHint:
if (hintText!=null) {
hintText.setText("網路異常,無法重新整理,請檢查網路");
}
break;
}
}
}
- iv_vs_content.xml --要通過ViewStub展示出來的內容
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:src="@mipmap/ic_launcher"/>
<TextView
android:id="@+id/tv_vsContent"
android:layout_width="match_parent"
android:layout_height="@dimen/dp30"
android:gravity="center"
android:text="eeee"/>
</LinearLayout>
(4) 使用總結
總結:
1)ViewStub 引用佈局時使用layout 屬性,取值@layout/xxxxx
2)InflateId 表示給被引用的佈局的id。也就是被控制顯示或隱藏的佈局的id.
3)如果不寫InflateId ,如果需要的話也可以直接在被引用的佈局中給出id屬性
4)inflate() 方法只能被呼叫一次,如果再次呼叫會報異常資訊 ViewStub must have a non-null ViewGroup viewParent。
這是因為,當ViewStub 呼叫inflate() 將其引用的 佈局/view 展示出來之後,ViewStub本身就會從檢視樹中被移除,此時viewStub 就獲取不到他的 父佈局, 而 inflate() 方法中,上來就需要獲取它的父佈局,然後根據父佈局是否為空再去執行具體的填充邏輯,如果為空就報上面的錯,所以,inflate() 之後如果還想再次顯示ViewStub 引用的佈局/view 就需要 在呼叫inflate() 的時候try catch,當 catch 到異常的時候,呼叫setVisibility()設定viewStub 的View.Visible即可。ViewStub類中Inflate() 的具體邏輯如下:
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
final View view = factory.inflate(mLayoutResource, parent,
false);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
mInflatedViewRef = new WeakReference<View>(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");
}
}
5) ViewStub的setVisibility()中也呼叫了inflate(),但是為什麼多次呼叫setVisibility()不會導致崩潰呢?
ViewStub 的setVisibility() 方法中,會先判斷 WeakReference 型別的成員變數 mInflatedViewRef 是否為空。第一次呼叫setVisibility()的時候,mInflatedViewRef並沒有初始化,也就是說是null,那麼這時候就會走inflate(),在inflate() 方法中給被填充起來的佈局/view建立一個WeakReference弱引用,並賦值給mInflatedViewRef,從而完成mInflatedViewRef的初始化。當第二次走setVisibility() 的時候,mInflatedViewRef已經不再是null,就會呼叫 WeakReference 的父類Reference 中的get() 方法獲取該引用指向的實體物件,也就是說通過get() 拿到 被填充的view物件,然後再走View類的setVisibility()。ViewStub類中的setVisibility()具體實現如下:
@Override
@android.view.RemotableViewMethod
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
View view = 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();
}
}
}
- 根據上面 第4點和第5點可以得出如下結論:
(1) ViewStub 的inflate() 只能被呼叫一次!
(2) 如果想控制/修改 被填充佈局中的內容並重復顯示被填充的view,就用try 將viewStub.inflate() 以及修改內容的程式碼包裹起來,並在catch 中setVisibility.
7) 在xml 中定義ViewStub 節點時,內部不能包含其他節點,也就是說,ViewStub 是一個自閉合節點,如果一個佈局/view如果想通過ViewStub顯示,只能定義在單獨的xml 檔案中。
版權宣告:
****以上內容均為原創,如需轉載請註明出處****
如文章中有不當之處,敬請指正。
相關文章
- Github使用介紹Github
- Tmux使用介紹UX
- saltstack使用介紹
- Redis介紹和使用Redis
- ChatGPT API使用介紹ChatGPTAPI
- 軟體介紹使用
- Github for Windows使用介紹GithubWindows
- 前端【Vuex】【使用介紹】前端Vue
- vagrant介紹及使用
- IIS Express介紹與使用Express
- 安卓註解使用介紹安卓
- Docker基本介紹及使用Docker
- java ShutdownHook介紹與使用JavaHook
- SVN命令列使用介紹命令列
- Influxdb 介紹與使用UX
- layui 的基本使用介紹UI
- Gin框架介紹及使用框架
- JQuery的介紹與使用jQuery
- Docker(二):Dockerfile 使用介紹Docker
- Guava ListenableFuture介紹以及使用Guava
- JPVideoPlayer 3.0 使用介紹IDE
- webpack.DefinePlugin使用介紹WebPlugin
- Nginx主程式使用介紹Nginx
- Tengine 常用模組使用介紹
- LangChain的Agent使用介紹LangChain
- ddddocr基本使用和介紹
- certutil工具的使用介紹
- LayerMask 的介紹和使用
- Burp Suite使用介紹(四)UI
- Burp Suite使用介紹(三)UI
- Burp Suite使用介紹(二)UI
- iOS Runtime介紹和使用iOS
- GoogleTagManager 介紹與使用Go
- Android 動畫 介紹與使用Android動畫
- Java Selenide 介紹&使用JavaIDE
- http代理使用分類介紹HTTP
- Python JWT 介紹和使用PythonJWT
- Charles 功能介紹和使用教程