Android Kotlin 基礎知識 02.4:資料繫結基礎知識

這個程式碼研究室是 Android Kotlin 基礎知識課程的一部分。如果您按部就班完成程式碼研究室,就能充分體驗到本課程的價值。所有課程程式碼研究室都列在 Android Kotlin 基礎知識程式碼研究室到達網頁

簡介

在本課程先前的程式碼研究室中,您使用 findViewById() 函式取得檢視區塊的參照。如果應用程式的檢視區塊階層複雜,Android 就會從根目錄開始遍歷檢視區塊階層,直到找到所需檢視區塊為止,因此 findViewById() 的成本很高,會拖慢應用程式速度。幸好有更好的方法。

如要在檢視畫面中設定資料,您已使用字串資源,並從活動中設定資料。如果檢視畫面瞭解資料,效率會更高。幸好,這項功能可以實現。

在本程式碼研究室中,您將瞭解如何使用資料繫結,免除 findViewById() 的需求。您也會瞭解如何使用資料繫結,直接從檢視區塊存取資料。

必備知識

您必須已經熟悉下列項目:

  • 什麼是活動,以及如何在 onCreate() 中使用版面配置設定活動。
  • 建立文字檢視區塊,並設定文字檢視區塊顯示的文字。
  • 使用 findViewById() 取得檢視區塊的參照。
  • 建立及編輯檢視區塊的基本 XML 版面配置。

課程內容

  • 如何使用資料繫結程式庫,移除對 findViewById() 發出的低效率呼叫。
  • 如何直接從 XML 存取應用程式資料。

學習內容

  • 修改應用程式,改用資料繫結而非 findViewById(),並直接從版面配置 XML 檔案存取資料。

在本程式碼研究室中,您將從 AboutMe 應用程式著手,並變更應用程式以使用資料繫結。完成後,應用程式的外觀會完全相同!

AboutMe 應用程式的功能如下:

  • 使用者開啟應用程式時,應用程式會顯示名稱、輸入暱稱的欄位、「完成」按鈕、星號圖片和可捲動的文字。
  • 使用者可以輸入暱稱,然後輕觸「完成」按鈕。可編輯的欄位和按鈕會替換為文字檢視畫面,顯示輸入的暱稱。


您可以使用在先前程式碼研究室中建構的程式碼,也可以從 GitHub 下載 AboutMeDataBinding-Starter 程式碼。

您在先前的程式碼研究室中編寫的程式碼,會使用 findViewById() 函式取得檢視區塊的參照。

每次在建立或重建檢視區塊後,使用 findViewById() 搜尋檢視區塊時,Android 系統都會在執行階段掃遍檢視區塊階層,找出該檢視區塊。如果應用程式只有少數幾個檢視區塊,這不會是問題。不過,正式版應用程式的版面配置可能包含數十個檢視區塊,即使設計再好,也會有巢狀檢視區塊。

試想一個線性版面配置,其中包含捲動檢視畫面,而捲動檢視畫面又包含文字檢視畫面。如果檢視區塊階層較大或較深,尋找檢視區塊可能需要相當長的時間,導致應用程式明顯變慢。在變數中快取檢視區塊有助於解決這個問題,但您仍須為每個命名空間中的每個檢視區塊初始化變數。如果檢視畫面和活動數量眾多,這也會造成負擔。

其中一種解決方法是建立物件,其中包含每個檢視區塊的參照。這個物件稱為 Binding 物件,可供整個應用程式使用。這項技術稱為「資料繫結」。為應用程式建立繫結物件後,您就能透過該物件存取檢視區塊和其他資料,不必遍歷檢視區塊階層或搜尋資料。

資料繫結具有下列優點:

  • 與使用 findByView() 的程式碼相比,程式碼較短,更容易閱讀及維護。
  • 資料和檢視畫面清楚分開。在本課程稍後,資料繫結的這項優點會越來越重要。
  • Android 系統只會遍歷檢視區塊階層一次來取得每個檢視區塊,而且是在應用程式啟動期間,而不是在使用者與應用程式互動時。
  • 您可取得存取檢視區塊的型別安全。(類型安全表示編譯器會在編譯時驗證類型;如果您試圖將錯誤的類型指派給變數,就會發生錯誤。)

在這項工作中,您要設定資料繫結,並使用資料繫結將 findViewById() 的呼叫替換為繫結物件的呼叫。

步驟 1:啟用資料繫結

如要使用資料繫結,您必須在 Gradle 檔案中啟用資料繫結,因為這項功能預設為停用。這是因為資料繫結會增加編譯時間,並可能影響應用程式啟動時間。

  1. 如果您沒有先前程式碼研究室的 AboutMe 應用程式,請從 GitHub 取得 AboutMeDataBinding-Starter 程式碼。在 Android Studio 中開啟。
  2. 開啟 build.gradle (Module: app) 檔案。
  3. android 區段中,在右大括號前新增 dataBinding 區段,並將 enabled 設為 true
dataBinding {
    enabled = true
}
  1. 出現提示時,請同步專案。如果系統未提示,請依序選取「File」>「Sync Project with Gradle Files」
  2. 您可以執行應用程式,但不會看到任何變更。

步驟 2:變更版面配置檔案,以便搭配資料繫結使用

如要使用資料繫結,您必須以 <layout> 標記包裝 XML 版面配置。這樣一來,根類別就不再是檢視區塊群組,而是包含檢視區塊群組和檢視區塊的版面配置。繫結物件隨後就能瞭解版面配置和其中的檢視區塊。

  1. 開啟 activity_main.xml 檔案。
  2. 切換至「文字」分頁標籤。
  3. <layout></layout> 新增為 <LinearLayout> 周圍最外層的標記。
<layout>
   <LinearLayout ... >
   ...
   </LinearLayout>
</layout>
  1. 依序選擇「Code」>「Reformat code」,修正程式碼縮排。

    版面配置的命名空間宣告必須位於最外層的標記中。
  1. <LinearLayout> 剪下命名空間宣告,然後貼到 <layout> 標記中。開啟的 <layout> 標記應如下所示,且 <LinearLayout> 標記只應包含檢視畫面屬性。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
  1. 建構並執行應用程式,確認您已正確完成這項操作。

步驟 3:在主要活動中建立繫結物件

將繫結物件的參照新增至主要活動,以便存取檢視區塊:

  1. 開啟 MainActivity.kt 檔案。
  2. onCreate() 之前,於頂層建立繫結物件的變數。這個變數通常稱為 binding

    編譯器會專為這個主要活動建立 binding 的型別 (即 ActivityMainBinding 類別)。名稱衍生自版面配置檔案名稱,也就是 activity_main + Binding
private lateinit var binding: ActivityMainBinding
  1. 如果 Android Studio 顯示提示,請匯入 ActivityMainBinding。如果系統未提示,請按一下 ActivityMainBinding 並按下 Alt+Enter 鍵 (Mac 上為 Option+Enter 鍵),匯入這個缺少的類別。(如需更多鍵盤快速鍵,請參閱「鍵盤快速鍵」一文。)

    import 陳述式應類似下圖所示。
import com.example.android.aboutme.databinding.ActivityMainBinding

接著,將目前的 setContentView() 函式替換為可執行下列動作的指令:

  • 建立繫結物件。
  • 使用 DataBindingUtil 類別的 setContentView() 函式,將 activity_main 版面配置與 MainActivity 建立關聯。這個 setContentView() 函式也會處理檢視區塊的部分資料繫結設定。
  1. onCreate() 中,將 setContentView() 呼叫替換為下列程式碼行。
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  1. 匯入 DataBindingUtil
import androidx.databinding.DataBindingUtil

步驟 4:使用繫結物件取代所有 findViewById() 呼叫

您現在可以將對 findViewById() 的所有呼叫,替換為繫結物件中檢視區塊的參照。建立繫結物件時,編譯器會從版面配置中的檢視區塊 ID 產生繫結物件中的檢視區塊名稱,並將其轉換為駝峰式大小寫。舉例來說,繫結物件中的 done_buttondoneButtonnickname_edit 會變成 nicknameEdit,而 nickname_text 會變成 nicknameText

  1. onCreate() 中,將使用 findViewById() 尋找 done_button 的程式碼,替換為參照繫結物件中按鈕的程式碼。

    將此程式碼:findViewById<Button>(R.id.done_button)
    替換為:binding.doneButton

    onCreate() 中設定點擊事件接聽程式的完成程式碼應如下所示。
binding.doneButton.setOnClickListener {
   addNickname(it)
}
  1. addNickname() 函式中的所有 findViewById() 呼叫執行相同操作。
    將所有 findViewById<View>(R.id.id_view) 替換為 binding.idView。請按照下列步驟操作:
  • 刪除 editTextnicknameTextView 變數的定義,以及對 findViewById() 的呼叫。這會導致錯誤。
  • 如要修正錯誤,請從 binding 物件取得 nicknameTextnicknameEditdoneButton 檢視區塊,而不是 (已刪除的) 變數。
  • binding.doneButton.visibility 取代 view.visibility。 使用 binding.doneButton 而不是傳入的 view,可讓程式碼更加一致。

    結果如下列程式碼所示:
binding.nicknameText.text = binding.nicknameEdit.text
binding.nicknameEdit.visibility = View.GONE
binding.doneButton.visibility = View.GONE
binding.nicknameText.visibility = View.VISIBLE
  • 功能維持不變。現在您可以選擇移除 view 參數,並更新 view 的所有用法,改為在這個函式中使用 binding.doneButton
  1. nicknameText 需要 String,而 nicknameEdit.textEditable。使用資料繫結時,必須將 Editable 明確轉換為 String
binding.nicknameText.text = binding.nicknameEdit.text.toString()
  1. 您可以刪除灰色的匯入項目。
  2. 使用 apply{} 將函式 Kotlin 化。
binding.apply {
   nicknameText.text = nicknameEdit.text.toString()
   nicknameEdit.visibility = View.GONE
   doneButton.visibility = View.GONE
   nicknameText.visibility = View.VISIBLE
}
  1. 建構並執行應用程式,應用程式的外觀和運作方式應與先前完全相同。

您可以善用資料繫結,直接將資料類別提供給檢視區塊。這項技術可簡化程式碼,對於處理更複雜的情況非常實用。

在本範例中,您會為名稱和暱稱建立資料類別,而不是使用字串資源設定名稱和暱稱。您可以使用資料繫結,讓檢視區塊存取資料類別。

步驟 1:建立 MyName 資料類別

  1. 在 Android Studio 的 java 目錄中,開啟 MyName.kt 檔案。如果沒有這個檔案,請建立名為 MyName.kt 的新 Kotlin 檔案。
  2. 為姓名和暱稱定義資料類別。使用空字串做為預設值。
data class MyName(var name: String = "", var nickname: String = "")

步驟 2:在版面配置中新增資料

activity_main.xml 檔案中,名稱目前是透過字串資源的 TextView 設定。您需要將名稱的參照替換為資料類別中資料的參照。

  1. 在「文字」分頁中開啟 activity_main.xml
  2. 在版面配置頂端的 <layout><LinearLayout> 標記之間,插入 <data></data> 標記。您可以在這裡將檢視畫面與資料連結。
<data>
  
</data>

在資料標記內,您可以宣告具名變數,其中包含類別的參照。

  1. <data> 標記中加入 <variable> 標記。
  2. 新增 name 參數,將變數命名為 "myName"。新增 type 參數,並將型別設為 MyName 資料類別的完整名稱 (套件名稱 + 變數名稱)。
<variable
       name="myName"
       type="com.example.android.aboutme.MyName" />

現在,您可以使用 myName 變數,而不必使用名稱的字串資源。

  1. 請將 android:text="@string/name" 替換為下列程式碼。

@={} 是用來取得大括號內參照資料的指令。

myName 會參照您先前定義的 myName 變數,該變數會指向 myName 資料類別,並從該類別擷取 name 屬性。

android:text="@={myName.name}"

步驟 3:建立資料

您現在可以參照版面配置檔案中的資料。接著,請建立實際資料。

  1. 開啟 MainActivity.kt 檔案,
  2. onCreate() 上方,建立私有變數,按照慣例也稱為 myName。將變數指派給 MyName 資料類別的執行個體,並傳遞名稱。
private val myName: MyName = MyName("Aleks Haecky")
  1. onCreate() 中,將版面配置檔案中的 myName 變數值設為您剛才宣告的 myName 變數值。您無法直接在 XML 中存取變數。您必須透過繫結物件存取。
binding.myName = myName
  1. 這時可能會顯示錯誤,因為您必須在變更後重新整理繫結物件。建構應用程式,錯誤應該就會消失。

步驟 4:在 TextView 中使用暱稱的資料類別

最後一個步驟,是在 TextView 中也使用暱稱的資料類別。

  1. 開啟 activity_main.xml
  2. nickname_text 文字檢視區塊中,新增 text 屬性。在資料類別中參照 nickname,如下所示。
android:text="@={myName.nickname}"
  1. ActivityMain 中,將
    nicknameText.text = nicknameEdit.text.toString()
    替換為在 myName 變數中設定暱稱的程式碼。
myName?.nickname = nicknameEdit.text.toString()

設定暱稱後,您希望程式碼使用新資料重新整理 UI。如要這麼做,您必須使所有繫結運算式失效,以便使用正確資料重新建立這些運算式。

  1. 設定暱稱後新增 invalidateAll(),讓 UI 根據更新後的繫結物件值重新整理。
binding.apply {
   myName?.nickname = nicknameEdit.text.toString()
   invalidateAll()
   ...
}
  1. 建構並執行應用程式,應用程式應可照常運作。

Android Studio 專案:AboutMeDataBinding

使用資料繫結取代 findViewById() 呼叫的步驟:

  1. build.gradle 檔案的 Android 區段中啟用資料繫結:
    dataBinding { enabled = true }
  2. 在 XML 版面配置中,使用 <layout> 做為根檢視區塊。
  3. 定義繫結變數:
    private lateinit var binding: ActivityMainBinding
  4. MainActivity 中建立繫結物件,並取代 setContentView
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  5. findViewById() 的呼叫替換為繫結物件中的檢視區塊參照。舉例來說:
    findViewById<Button>(R.id.done_button) ⇒ binding.doneButton
    (在本例中,檢視區塊名稱是從 XML 中的檢視區塊 id 以駝峰式大小寫產生)。

將檢視區塊繫結至資料的步驟:

  1. 為資料建立資料類別。
  2. <layout> 標記內新增 <data> 區塊。
  3. 定義具有名稱和資料類別型別的 <variable>
<data>
   <variable
       name="myName"
       type="com.example.android.aboutme.MyName" />
</data>
  1. MainActivity 中,建立含有資料類別例項的變數。例如:
    private val myName: MyName = MyName("Aleks Haecky")
  1. 在繫結物件中,將變數設為您剛建立的變數:
    binding.myName = myName
  1. 在 XML 中,將檢視區塊的內容設為您在 <data> 區塊中定義的變數。使用點標記法存取資料類別中的資料。
    android:text="@={myName.name}"

Udacity 課程:

Android 開發人員說明文件:

本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:

  • 視需要指派作業。
  • 告知學員如何繳交作業。
  • 為作業評分。

講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。

如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。

回答問題

第 1 題

為什麼要盡量減少對 findViewById() 的明確和隱含呼叫?

  • 每次呼叫 findViewById() 時,系統都會掃遍檢視區塊階層。
  • findViewById() 會在主執行緒或 UI 執行緒上執行。
  • 這些呼叫會讓使用者介面的速度變慢。
  • 降低應用程式當機的可能性。

第 2 題

你會如何描述資料繫結?

舉例來說,您可以這樣描述資料繫結:

  • 資料繫結的主要概念是在編譯時建立物件,將兩段相距遙遠的資訊連結/對應/繫結在一起,這樣您就不必在執行階段尋找資料。
  • 向您顯示這些繫結的物件稱為「繫結物件」
  • 繫結物件是由編譯器建立。

第 3 題

下列何者「不是」資料繫結的優點?

  • 程式碼的長度較短,因此較容易閱讀及維護。
  • 資料和檢視畫面清楚分開。
  • Android 系統只會掃遍檢視區塊階層一次,即可取得每個檢視區塊。
  • 呼叫 findViewById() 會產生編譯器錯誤。
  • 存取檢視區塊時的型別安全

第 4 題

<layout> 標記的功能是什麼?

  • 您可以在版面配置中將其包裝在根檢視畫面周圍。
  • 系統會為版面配置中的所有檢視區塊建立繫結。
  • 這會指定使用資料繫結的 XML 版面配置中的頂層檢視區塊。
  • 您可以在 <layout> 內使用 <data> 標記,將變數繫結至資料類別。

第 5 題

以下哪一個是在 XML 版面配置中參照繫結資料的正確方式?

  • android:text="@={myDataClass.property}"
  • android:text="@={myDataClass}"
  • android:text="@={myDataClass.property.toString()}"
  • android:text="@={myDataClass.bound_data.property}"

開始下一個課程:3.1:建立片段

如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 基礎知識程式碼研究室登陸頁面