1. 程式人生 > >Android進階——佈局優化之靈活藉助ViewStub實現懶載入

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之後
這裡寫圖片描述