這個程式碼研究室是 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 檔案中啟用資料繫結,因為這項功能預設為停用。這是因為資料繫結會增加編譯時間,並可能影響應用程式啟動時間。
- 如果您沒有先前程式碼研究室的 AboutMe 應用程式,請從 GitHub 取得 AboutMeDataBinding-Starter 程式碼。在 Android Studio 中開啟。
- 開啟
build.gradle (Module: app)
檔案。 - 在
android
區段中,在右大括號前新增dataBinding
區段,並將enabled
設為true
。
dataBinding {
enabled = true
}
- 出現提示時,請同步專案。如果系統未提示,請依序選取「File」>「Sync Project with Gradle Files」。
- 您可以執行應用程式,但不會看到任何變更。
步驟 2:變更版面配置檔案,以便搭配資料繫結使用
如要使用資料繫結,您必須以 <layout>
標記包裝 XML 版面配置。這樣一來,根類別就不再是檢視區塊群組,而是包含檢視區塊群組和檢視區塊的版面配置。繫結物件隨後就能瞭解版面配置和其中的檢視區塊。
- 開啟
activity_main.xml
檔案。 - 切換至「文字」分頁標籤。
- 將
<layout></layout>
新增為<LinearLayout>
周圍最外層的標記。
<layout>
<LinearLayout ... >
...
</LinearLayout>
</layout>
- 依序選擇「Code」>「Reformat code」,修正程式碼縮排。
版面配置的命名空間宣告必須位於最外層的標記中。
- 從
<LinearLayout>
剪下命名空間宣告,然後貼到<layout>
標記中。開啟的<layout>
標記應如下所示,且<LinearLayout>
標記只應包含檢視畫面屬性。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
- 建構並執行應用程式,確認您已正確完成這項操作。
步驟 3:在主要活動中建立繫結物件
將繫結物件的參照新增至主要活動,以便存取檢視區塊:
- 開啟
MainActivity.kt
檔案。 - 在
onCreate()
之前,於頂層建立繫結物件的變數。這個變數通常稱為binding
。
編譯器會專為這個主要活動建立binding
的型別 (即ActivityMainBinding
類別)。名稱衍生自版面配置檔案名稱,也就是activity_main + Binding
。
private lateinit var binding: ActivityMainBinding
- 如果 Android Studio 顯示提示,請匯入
ActivityMainBinding
。如果系統未提示,請按一下ActivityMainBinding
並按下Alt+Enter
鍵 (Mac 上為Option+Enter
鍵),匯入這個缺少的類別。(如需更多鍵盤快速鍵,請參閱「鍵盤快速鍵」一文。)import
陳述式應類似下圖所示。
import com.example.android.aboutme.databinding.ActivityMainBinding
接著,將目前的 setContentView()
函式替換為可執行下列動作的指令:
- 建立繫結物件。
- 使用
DataBindingUtil
類別的setContentView()
函式,將activity_main
版面配置與MainActivity
建立關聯。這個setContentView()
函式也會處理檢視區塊的部分資料繫結設定。
- 在
onCreate()
中,將setContentView()
呼叫替換為下列程式碼行。
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
- 匯入
DataBindingUtil
。
import androidx.databinding.DataBindingUtil
步驟 4:使用繫結物件取代所有 findViewById() 呼叫
您現在可以將對 findViewById()
的所有呼叫,替換為繫結物件中檢視區塊的參照。建立繫結物件時,編譯器會從版面配置中的檢視區塊 ID 產生繫結物件中的檢視區塊名稱,並將其轉換為駝峰式大小寫。舉例來說,繫結物件中的 done_button
是 doneButton
,nickname_edit
會變成 nicknameEdit
,而 nickname_text
會變成 nicknameText
。
- 在
onCreate()
中,將使用findViewById()
尋找done_button
的程式碼,替換為參照繫結物件中按鈕的程式碼。
將此程式碼:findViewById<Button>(R.id.
done_button
)
替換為:binding.doneButton
在onCreate()
中設定點擊事件接聽程式的完成程式碼應如下所示。
binding.doneButton.setOnClickListener {
addNickname(it)
}
- 對
addNickname()
函式中的所有findViewById()
呼叫執行相同操作。
將所有findViewById<
View
>(R.id.
id_view
)
替換為binding.
idView
。請按照下列步驟操作:
- 刪除
editText
和nicknameTextView
變數的定義,以及對findViewById()
的呼叫。這會導致錯誤。 - 如要修正錯誤,請從
binding
物件取得nicknameText
、nicknameEdit
和doneButton
檢視區塊,而不是 (已刪除的) 變數。 - 以
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
。
nicknameText
需要String
,而nicknameEdit.text
是Editable
。使用資料繫結時,必須將Editable
明確轉換為String
。
binding.nicknameText.text = binding.nicknameEdit.text.toString()
- 您可以刪除灰色的匯入項目。
- 使用
apply{}
將函式 Kotlin 化。
binding.apply {
nicknameText.text = nicknameEdit.text.toString()
nicknameEdit.visibility = View.GONE
doneButton.visibility = View.GONE
nicknameText.visibility = View.VISIBLE
}
- 建構並執行應用程式,應用程式的外觀和運作方式應與先前完全相同。
您可以善用資料繫結,直接將資料類別提供給檢視區塊。這項技術可簡化程式碼,對於處理更複雜的情況非常實用。
在本範例中,您會為名稱和暱稱建立資料類別,而不是使用字串資源設定名稱和暱稱。您可以使用資料繫結,讓檢視區塊存取資料類別。
步驟 1:建立 MyName 資料類別
- 在 Android Studio 的
java
目錄中,開啟MyName.kt
檔案。如果沒有這個檔案,請建立名為MyName.kt
的新 Kotlin 檔案。 - 為姓名和暱稱定義資料類別。使用空字串做為預設值。
data class MyName(var name: String = "", var nickname: String = "")
步驟 2:在版面配置中新增資料
在 activity_main.xml
檔案中,名稱目前是透過字串資源的 TextView
設定。您需要將名稱的參照替換為資料類別中資料的參照。
- 在「文字」分頁中開啟
activity_main.xml
。 - 在版面配置頂端的
<layout>
和<LinearLayout>
標記之間,插入<data></data>
標記。您可以在這裡將檢視畫面與資料連結。
<data>
</data>
在資料標記內,您可以宣告具名變數,其中包含類別的參照。
- 在
<data>
標記中加入<variable>
標記。 - 新增
name
參數,將變數命名為"myName"
。新增type
參數,並將型別設為MyName
資料類別的完整名稱 (套件名稱 + 變數名稱)。
<variable
name="myName"
type="com.example.android.aboutme.MyName" />
現在,您可以使用 myName
變數,而不必使用名稱的字串資源。
- 請將
android:text="@string/name"
替換為下列程式碼。
@={}
是用來取得大括號內參照資料的指令。
myName
會參照您先前定義的 myName
變數,該變數會指向 myName
資料類別,並從該類別擷取 name
屬性。
android:text="@={myName.name}"
步驟 3:建立資料
您現在可以參照版面配置檔案中的資料。接著,請建立實際資料。
- 開啟
MainActivity.kt
檔案, - 在
onCreate()
上方,建立私有變數,按照慣例也稱為myName
。將變數指派給MyName
資料類別的執行個體,並傳遞名稱。
private val myName: MyName = MyName("Aleks Haecky")
- 在
onCreate()
中,將版面配置檔案中的myName
變數值設為您剛才宣告的myName
變數值。您無法直接在 XML 中存取變數。您必須透過繫結物件存取。
binding.myName = myName
- 這時可能會顯示錯誤,因為您必須在變更後重新整理繫結物件。建構應用程式,錯誤應該就會消失。
步驟 4:在 TextView 中使用暱稱的資料類別
最後一個步驟,是在 TextView
中也使用暱稱的資料類別。
- 開啟
activity_main.xml
。 - 在
nickname_text
文字檢視區塊中,新增text
屬性。在資料類別中參照nickname
,如下所示。
android:text="@={myName.nickname}"
- 在
ActivityMain
中,將nicknameText.text = nicknameEdit.text.toString()
替換為在myName
變數中設定暱稱的程式碼。
myName?.nickname = nicknameEdit.text.toString()
設定暱稱後,您希望程式碼使用新資料重新整理 UI。如要這麼做,您必須使所有繫結運算式失效,以便使用正確資料重新建立這些運算式。
- 設定暱稱後新增
invalidateAll()
,讓 UI 根據更新後的繫結物件值重新整理。
binding.apply {
myName?.nickname = nicknameEdit.text.toString()
invalidateAll()
...
}
- 建構並執行應用程式,應用程式應可照常運作。
Android Studio 專案:AboutMeDataBinding
使用資料繫結取代 findViewById()
呼叫的步驟:
- 在
build.gradle
檔案的 Android 區段中啟用資料繫結:dataBinding { enabled = true }
- 在 XML 版面配置中,使用
<layout>
做為根檢視區塊。 - 定義繫結變數:
private lateinit var binding: ActivityMainBinding
- 在
MainActivity
中建立繫結物件,並取代setContentView
:binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
- 將
findViewById()
的呼叫替換為繫結物件中的檢視區塊參照。舉例來說:findViewById<Button>(R.id.done_button) ⇒ binding.doneBu
tton
(在本例中,檢視區塊名稱是從 XML 中的檢視區塊id
以駝峰式大小寫產生)。
將檢視區塊繫結至資料的步驟:
- 為資料建立資料類別。
- 在
<layout>
標記內新增<data>
區塊。 - 定義具有名稱和資料類別型別的
<variable>
。
<data>
<variable
name="myName"
type="com.example.android.aboutme.MyName" />
</data>
- 在
MainActivity
中,建立含有資料類別例項的變數。例如:private val myName: MyName = MyName("Aleks Haecky")
- 在繫結物件中,將變數設為您剛建立的變數:
binding.myName = myName
- 在 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}"
開始下一個課程:
如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 基礎知識程式碼研究室登陸頁面。