Android進階——佈局優化之靈活藉助ViewStub實現懶載入
引言
相信在開發Android App的過程中,我們會常常遇到這樣的業務需求,需要在執行時根據資料動態決定顯示或隱藏某個View和佈局。通常就是把可能用到的View先寫在佈局裡,再初始化其可見性都設為View.GONE,然後在程式碼中根據資料動態的更改它的可見性。雖然這樣的實現,邏輯簡單而且控制起來比較靈活。但是也存在一定的缺點耗費資源,即使把View的初始可見View.GONE但是在Inflate佈局的時候View仍然會被Inflate,即說仍然會建立物件,會被例項化,會被設定屬性從而導致耗費記憶體等資源。今天推薦一種新的機制——ViewStub,但要根據自己的業務需求來靈活使用。
一、ViewStub概述
ViewStub 直接繼承自View,是一種不可見,0大小的可以在執行的時候再載入的View(A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime)。僅只有在呼叫inflate()進行對映內容佈局之後(值得注意的是ViewStub只能inflate一次,再次進行inflate的時候會報異常)或者設定為Visibility時才可見,功能上可以看成是高階的< include >標籤(一個把其它佈局資源包含進某個特定的佈局中,有點類似其他開發語言中的模板概念)。
二、ViewStub的特點、適用場景及注意事項
1、ViewStub的一些特點
ViewStub只能被Inflate一次,inflate之後ViewStub物件就會被置為空。即某個被ViewStub指定的佈局被Inflate後,就不能夠再通過ViewStub來控制它了。
ViewStub只能用來Inflate一個佈局檔案,而不是某個具體的View,當然也可以把View寫在某個佈局檔案中。
2、ViewStub的適用場景及注意事項
在程式的執行期間,某個佈局在被Inflate後,就不會有變化,除非重新啟動。因為ViewStub只能Inflate一次,inflate之後就不能直接使用ViewStub來控制佈局。
想要控制顯示與隱藏的是一個佈局檔案,而非某個View。因為設定給ViewStub的只能是某個佈局檔案的Id,所以無法讓它來直接控制某個View。所以,如果想要控制某個View的顯示與隱藏,抑或想要在執行時不斷的顯示與隱藏某個佈局或View,只能使用View的可見性來控制。
二、ViewStub的使用步驟
ViewStub支援在程式執行的過程中通過懶載入的模式inflate佈局資源中。只有當一個ViewStub的inflate()方法被呼叫或者被設為View.VISIBILITY時,此時ViewStub會把設定的佈局才會被建立對應的物件和例項化,並替換當前ViewStub的位置,顯示相應的效果。雖然一開始ViewStub就存在於檢視樹中,但是直到setVisibility(int)或inflate()方法被呼叫時才消耗資源,否則是不載入控制元件的,因此消耗的資源小。這就是所謂的”懶載入”。和< include >標籤一樣可以看成是一個“佔位符“,可以看成自身是不呈現任何UI效果的檢視容器,主要就是用於存放真實的佈局和檢視,所以除了設定必要的尺寸屬性和位置之外,通常必須設定三個重要屬性和一個回撥監聽介面:
android:id——ViewStub 自身的Id,無論是否被inflate,都可以通過findViewById拿到對應的ViewStub控制元件本身。
android:inflatedId——ViewStub設定的被對映的佈局檔案中的跟節點的Id,inflate之後可以通過findViewById獲取到對應的被對映的佈局物件。
android:layout——將要對映的佈局檔名,注意和include 標籤裡的區分(include標籤是layout:)
ViewStub.OnInflateListener——當ViewStub成功對映預先設定的佈局會觸發回撥(Listener used to receive a notification after a ViewStub has successfully inflated its layout resource)
ViewStub的優勢在於在某些場景中,並不一定需要把所有的內容都展示出來,可以隱藏一些View檢視,待使用者需要展示的時候再載入到當前的Layout中,這個時候就可以用到ViewStub這個控制元件了,這樣可以減少資源的消耗,使最初的載入速度變快。
三、簡單使用ViewStub
1、首先建立將要被對映的佈局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container_erro_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@mipmap/bg_exit_dialog"
android:layout_gravity="center"
android:orientation="vertical">
<ImageView
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@mipmap/ic_erro_tips"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/tv_erro_tips"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#ffffff"
/>
</LinearLayout>
2、建立主佈局引入ViewStub
<?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:background="@mipmap/bg_activity">
<ViewStub
android:id="@+id/contentPanel"
android:inflatedId="@+id/inflatedStart"
android:layout="@layout/layout_no_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="showViewStub"
android:layout_alignParentBottom="true"
android:layout_marginBottom="48dp"
android:text="showViewStub"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="hideViewStub"
android:layout_alignParentBottom="true"
android:text="hideViewStub"/>
</RelativeLayout>
3、實現MainActivity
package com.crazymo.swiperefreshlayout;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewStub;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ViewStubActivity extends AppCompatActivity implements ViewStub.OnInflateListener {
private ViewStub viewStub;
private LinearLayout parentContainer;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_stub);
init();
}
private void init() {
viewStub= (ViewStub) findViewById(R.id.contentPanel);
viewStub.setOnInflateListener(this);
}
@Override
public void onInflate(ViewStub stub, View inflated) {
//inflate ViewStub的時候顯示
Log.e("ViewStub","ViewStub is loaded! viewStub==null"+(viewStub==null));
}
public void showViewStub(View view){
showViewStub();
}
private void showViewStub() {
try {
Log.e("ViewStub","ViewStub load before! viewStub==null"+(viewStub==null));
parentContainer = (LinearLayout) viewStub.inflate();
textView = (TextView) parentContainer.findViewById(R.id.tv_erro_tips);
textView.setText("不好意思,還未錄入任何資料");
}catch (Exception e){
if(parentContainer==null) {
parentContainer = (LinearLayout) findViewById(R.id.inflatedStart);
}
if(textView==null) {
textView = (TextView) parentContainer.findViewById(R.id.tv_erro_tips);
}
textView.setText("不好意思,還未錄入任何資料");
viewStub.setVisibility(View.VISIBLE);
}
}
public void hideViewStub(View view){
viewStub.setVisibility(View.GONE);
}
}
初始化和隱藏ViewStub之後MainActivity的佈局層次結構
顯示ViewStub之後