本程式碼研究室是 Android Kotlin 基礎課程的一部分。使用程式碼研究室逐步完成程式碼課程後,您將能充分發揮本課程的潛能。所有課程程式碼研究室清單均列於 Android Kotlin 基礎程式碼研究室到達網頁。
簡介
在先前的程式碼研究室中,你更新了 TrackMySleepquality 應用程式,以顯示 RecyclerView
中的睡眠品質相關資料。您在第一次建立 RecyclerView
時學到的技巧已經足以應付大多數RecyclerViews
清單,這些 <不過,有些技術可讓 RecyclerView
更有效率地處理大型清單,而且程式碼的維護和複雜度也比較容易維護並擴充。
在這個程式碼研究室中,您將從先前的程式碼研究室的睡眠追蹤應用程式中進行建構。你能夠更有效率地更新睡眠資料清單,並瞭解如何將資料繫結與 RecyclerView
搭配使用。(如果您沒有舊版程式碼研究室中的應用程式,可以下載這個程式碼研究室的範例程式碼)。
須知事項
- 運用活動、片段和檢視來建構基本的使用者介面。
- 在片段之間瀏覽,並使用
safeArgs
在片段之間傳送資料。 - 查看模型、查看模型工廠、轉換,以及
LiveData
及其觀測器。 - 如何建立
Room
資料庫、建立 DAO 以及定義實體。 - 如何使用協同程式處理資料庫和其他長時間執行的工作。
- 如何實作具有
Adapter
、ViewHolder
和項目版面配置的基本RecyclerView
。
課程內容
- 如何使用
DiffUtil
,有效率地更新RecyclerView
顯示的清單。 - 如何使用
RecyclerView
的資料繫結。 - 如何使用繫結轉接程式轉換資料。
執行步驟
- 根據上一系列程式碼研究室的 TrackMySleepquality 應用程式進行建構。
- 更新
SleepNightAdapter
,使用DiffUtil
有效率地更新清單。 - 使用繫結轉接程式轉換資料,以實作
RecyclerView
的資料繫結。
睡眠追蹤器應用程式有兩個畫面,以片段表示,如下圖所示。
第一個畫面 (如左側所示) 為開始和停止追蹤的按鈕。螢幕會顯示使用者的某些睡眠資料。[清除] 按鈕永久刪除應用程式已收集的所有資料。第二個螢幕則是根據選取睡眠品質評分的方式。
這個應用程式的建築設計是使用 UI 控制器 (ViewModel
和 LiveData
) 及 Room
資料庫來保留睡眠資料。
睡眠資料會顯示於 RecyclerView
。在這個程式碼研究室中,您需要為 RecyclerView
建構 DiffUtil
和資料繫結部分。完成這個程式碼研究室後,您的應用程式外觀會完全相同,但將更有效率,更容易進行擴充和維護。
您可以繼續使用先前的程式碼研究室提供的 SleepTracker 應用程式,或者從 GitHub 下載 RecyclerViewDiffUtilDatabinding-Starter 應用程式。
- 如有需要,請從 GitHub 下載 RecyclerViewDiffUtilDatabinding-Starter 應用程式,然後在 Android Studio 中開啟專案。
- 執行應用程式。
- 開啟
SleepNightAdapter.kt
檔案。 - 請檢查程式碼,熟悉應用程式的結構。請參閱下圖,回顧如何搭配
RecyclerView
搭配轉接模式來向使用者顯示睡眠資料。
- 應用程式會根據使用者輸入內容建立
SleepNight
物件清單,每個SleepNight
物件都代表一晚的睡眠、時間長度和品質。 SleepNightAdapter
會將SleepNight
物件清單調整成RecyclerView
可使用和顯示的內容。SleepNightAdapter
轉接程式產生的ViewHolders
包含可檢視資料的檢視、資料和中繼資訊,以顯示這些資料。RecyclerView
使用「SleepNightAdapter
」決定要顯示多少項目 (getItemCount()
)。「RecyclerView
」會使用「onCreateViewHolder()
」和「onBindViewHolder()
」取得繫結的資料顯示對象。
NotificationsDataSetChanged() 方法的效率較低
如要通知 RecyclerView
清單中的項目已變更且需要更新,目前的程式碼會在 SleepNightAdapter
中呼叫 notifyDataSetChanged()
,如下所示。
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}
不過,notifyDataSetChanged()
會告訴 RecyclerView
整個清單可能無效。因此,RecyclerView
會重新組合並重整清單中的每個項目,包括未顯示在畫面上的項目。因此需要多花工夫。如果是較大型或複雜的清單,只要使用者捲動清單,顯示畫面就會讓畫面閃爍或變慢。
如要修正這個問題,您可以告知「RecyclerView
」有哪些異動。如此一來,「RecyclerView
」就只能更新畫面上的變更檢視畫面。
RecyclerView
有一個功能豐富的 API,可用來更新單一元素。您可以透過notifyItemChanged()
通知 RecyclerView
該項目已變更,也可以對新增、移除或移動的項目執行類似功能。您可以手動執行這些動作,但是該工作並不容易,而且可能涉及大量的程式碼。
幸運的是,這是更好的方式。
DiffUtil 兼具效率與便利性
RecyclerView
有一個名為 DiffUtil
的類別,用於計算兩份清單的差異。DiffUtil
使用一份新清單和一份新清單,以瞭解其中的差異。這項功能會找出新增、移除或變更的項目。它使用 Eugene W. Myers 的差分算法 找出滿生列表生成新更改所需的最小數量。
「DiffUtil
」找出變更項目後,可以使用該資訊只更新已變更、新增、移除或移動的項目,這比重做整份清單更有效率。
在這項工作中,您會將 SleepNightAdapter
升級為使用 DiffUtil
,以針對 RecyclerView
的資料進行最佳化調整。
步驟 1:實作 SleepNightDiffCallback
如要使用 DiffUtil
類別的功能,請擴充 DiffUtil.ItemCallback
。
- 開啟
SleepNightAdapter.kt
。 - 在
SleepNightAdapter
的完整類別定義下方建立一個新的SleepNightDiffCallback
頂層類別,擴充DiffUtil.ItemCallback
。傳遞SleepNight
做為一般參數。
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
}
- 將遊標放到
SleepNightDiffCallback
類別名稱中。 - 按
Alt+Enter
(Mac 上Option+Enter
)然後選取執行成據。 - 在隨即開啟的對話方塊中,按下 shift-left-click 以選取
areItemsTheSame()
和areContentsTheSame()
方法,然後按一下 [OK]。
這將產生兩個方法,在SleepNightDiffCallback
中產生虛設常式,如下所示。DiffUtil
使用上述兩種方法來確定清單和項目的變更方式。
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
- 在
areItemsTheSame()
中,將TODO
換成程式碼,以測試通過的SleepNight
項目oldItem
和newItem
是否相同。如果商品具有相同的nightId
,則代表相同的商品,所以傳回true
。如果沒有,則傳回false
。「DiffUtil
」會使用這項測試作業來確認該項目是否已新增、移除或移動。
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem.nightId == newItem.nightId
}
- 在
areContentsTheSame()
內,檢查oldItem
和newItem
是否包含相同的資料,也就是是否相同。這項平等檢查會檢查所有欄位,因為SleepNight
是資料類別。Data
類別會自動為您定義equals
和其他幾種方法。如果oldItem
和newItem
之間有任何差異,這個程式碼可告知DiffUtil
已更新該項目。
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem == newItem
}
這是使用 RecyclerView
顯示變更清單的常見模式。RecyclerView
提供轉接器類別 ListAdapter
,可協助您建立由清單支援的 RecyclerView
轉接器。
ListAdapter
會追蹤清單,並在更新清單時通知轉接程式。
步驟 1:變更轉接程式,藉此擴充 ListAdapter
- 在
SleepNightAdapter.kt
檔案中,變更SleepNightAdapter
的類別簽名以延長ListAdapter
。 - 如果出現系統提示,請匯入
androidx.recyclerview.widget.ListAdapter
。 - 在
SleepNightAdapter.ViewHolder
之前,將SleepNight
新增為ListAdapter
的第一個引數。 - 將
SleepNightDiffCallback()
新增為建構函式的參數。「ListAdapter
」會使用這項資料來辨識清單的變更項目。你完成的「SleepNightAdapter
」課程簽名應如下所示。
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
- 在
SleepNightAdapter
類別內,刪除data
欄位 (包括 setter)。「ListAdapter
」已經為您追蹤這個清單了,您不再需要這項功能。 - 刪除
getItemCount()
的覆寫值,因為ListAdapter
會為您導入這個方法。 - 如要清除
onBindViewHolder()
中的錯誤,請變更item
變數。與其使用data
取得item
,請呼叫ListAdapter
提供的getItem(position)
方法。
val item = getItem(position)
步驟 2:使用 submitList() 持續更新清單
有變更清單時,您的程式碼必須通知 ListAdapter
。ListAdapter
提供了名為 submitList()
的方法,可告知 ListAdapter
有新的清單可用。呼叫這個方法時,ListAdapter
會將新清單與舊清單區分,並偵測新增、移除、移動或變更的項目。然後 ListAdapter
會更新 RecyclerView
顯示的項目。
- 開啟
SleepTrackerFragment.kt
。 - 在
onCreateView()
的觀測器中,找出參照了data
變數的錯誤訊息。 - 將
adapter.data = it
換成對adapter.submitList(it)
的呼叫。更新後的程式碼如下所示。
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
- 執行你的應用程式。由於執行速度越快,執行速度可能較慢。
在這項工作中,您將使用和之前的程式碼研究室相同的技術設定資料繫結,並消除對 findViewById()
的呼叫。
步驟 1:在版面配置檔案中加入資料繫結
- 在「Text」分頁中開啟
list_item_sleep_night.xml
版面配置檔案。 - 將遊標移到
ConstraintLayout
標記上,然後按下Alt+Enter
(在 Mac 上為Option+Enter
)。系統會開啟意圖選單 (「快速修正」選單)。 - 選取 [轉換為資料繫結版面配置]。這麼做會將版面配置包裝成
<layout>
並在其中加入<data>
標記。 - 視需要捲動至頂端,然後在
<data>
標記中宣告名為sleep
的變數。 - 將
type
設為SleepNight
的完整名稱,com.example.android.trackmysleepquality.database.SleepNight
。完成的<data>
標記應如下所示。
<data>
<variable
name="sleep"
type="com.example.android.trackmysleepquality.database.SleepNight"/>
</data>
- 如要強制建立
Binding
物件,請選取 [Create > Clean Project] (建構專案;清理專案),然後選取 [Create > Rebuild Project] (建構專案;重新建構專案)。(如果問題仍無法解決,請選取 [檔案] > [撤銷快取 / 重新啟動])。ListItemSleepNightBinding
繫結物件和相關程式碼,會新增至專案產生的檔案。
步驟 2:使用資料繫結叫用項目版面配置
- 開啟
SleepNightAdapter.kt
。 - 在
ViewHolder
類別中,尋找from()
方法。 - 刪除
view
變數的宣告。
刪除程式碼:
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
- 在
view
變數中,請定義名為binding
的新變數,以叫用ListItemSleepNightBinding
繫結物件,如下所示。對所需的繫結物件進行必要的匯入。
val binding =
ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
- 函式結尾將傳回
binding
,而非傳回view
。
return ViewHolder(binding)
- 如要清除錯誤,請將遊標移至「
binding
」這個字詞。按下Alt+Enter
(在 Mac 上為Option+Enter
) 開啟意圖選單。
- 選擇變更參數 'itemView' 類別 'ViewHolder' 的主要建構函式類型 — 至 'ListItemSleepNightbinding'這會更新
ViewHolder
類別的參數類型。
- 捲動至「
ViewHolder
」的課程定義,即可在簽名中查看變更。您看到itemView
的錯誤,因為您在from()
方法中將itemView
變更為binding
。
在ViewHolder
類別定義中,請在itemView
的任一出現右鍵上按一下滑鼠右鍵,然後選取 [Re 因素] > [重新命名]。將名稱變更為「binding
」。 - 將建構函式參數
binding
替換為val
,使其成為屬性。 - 在對父類別
RecyclerView.ViewHolder
的呼叫中,將參數從binding
變更為binding.root
。您必須傳送View
,而binding.root
是項目版面配置中的根ConstraintLayout
。 - 您完成的課程宣告應如下方所示。
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){
您也會看到向 findViewById()
發出的呼叫,而您將修正此錯誤。
步驟 3:取代 FindViewById()
您現在可以將 sleepLength
、quality
和 qualityImage
屬性更新為使用 binding
物件,而不是 findViewById()
。
- 將
sleepLength
、qualityString
和qualityImage
的初始化設定變更為使用binding
物件的檢視畫面,如下所示。之後,您的驗證碼應該就不會再顯示任何錯誤。
val sleepLength: TextView = binding.sleepLength
val quality: TextView = binding.qualityString
val qualityImage: ImageView = binding.qualityImage
建立繫結物件後,您就不需要定義 sleepLength
、quality
和 qualityImage
屬性。DataBinding
會快取查詢,因此不需宣告這些屬性。
- 在
sleepLength
、quality
和qualityImage
屬性名稱上按一下滑鼠右鍵。選取 [重構 > 內嵌],或按下Control+Command+N
(在 Mac 上為Option+Command+N
)。 - 執行應用程式 (如果專案發生錯誤,您可能需要清理及重建專案)。
在這項工作中,您將應用程式升級,以便透過繫結繫結設定資料繫結,藉此在資料檢視中設定資料。
在先前的程式碼研究室中,您已使用 Transformations
類別取得 LiveData
並產生格式化文字以顯示在文字檢視中。不過,如果您需要繫結其他類型或複雜類型,可以提供繫結轉接程式,協助資料繫結使用這些類型。繫結轉接器是一種轉接器,可接收相關資料並將資料調整至可繫結檢視的內容 (例如文字或圖片)。
您將會導入三個繫結轉接器,一個用於品質圖片,一個則用於每個文字欄位。總結來說,如要宣告繫結轉接程式,您必須定義一個方法來擷取項目和一個視圖,並使用 @BindingAdapter
加上註解。在方法內容中,您將執行轉換作業。在 Kotlin 中,您可以將繫結轉接程式寫入為接收資料的檢視類別的擴充函式。
步驟 1:建立繫結轉接程式
請注意,您必須在此步驟中匯入一些類別,並不會逐一呼叫。
- 開啟
SleepNightAdapater.kt
。 - 在
ViewHolder
類別內找到bind()
方法,並提醒自己這個方法的用途。您會取得計算binding.sleepLength
、binding.quality
和binding.qualityImage
值的程式碼,並改為在轉接程式內使用。(目前請保留您原本的程式碼,日後請繼續移動)。 - 在
sleeptracker
套件中,建立及開啟名為BindingUtils.kt
的檔案。 - 在
TextView
上宣告名為setSleepDurationFormatted
的擴充函式,並傳入SleepNight
。此函式將成為轉接程式,用於計算睡眠時間及進行格式化。
fun TextView.setSleepDurationFormatted(item: SleepNight) {}
- 在
setSleepDurationFormatted
的主體中,將資料繫結至資料檢視的方式與ViewHolder.bind()
相同。呼叫convertDurationToFormatted()
,然後將TextView
的text
設為格式化文字。(由於這是TextView
的擴充功能函式,您可以直接存取text
屬性)。
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
- 如要提供這個繫結轉接程式的資料繫結,請使用
@BindingAdapter
為函式加上註解。 - 此函式是
sleepDurationFormatted
屬性的轉接器,因此請將sleepDurationFormatted
做為引數傳遞至@BindingAdapter
。
@BindingAdapter("sleepDurationFormatted")
- 第二個轉接程式會根據
SleepNight
物件的值,設定睡眠品質。在TextView
上建立名為setSleepQualityString()
的擴充功能函式,並傳入SleepNight
。 - 在主體中,將資料繫結至資料檢視的方式與
ViewHolder.bind()
相同。呼叫convertNumericQualityToString
並設定text
。 - 使用
@BindingAdapter("sleepQualityString")
為函式加上註解。
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
- 第三個繫結轉接程式可將圖片設為圖片檢視。在
ImageView
中建立擴充功能函式,呼叫setSleepImage
,並使用ViewHolder.bind()
的程式碼,如下所示。
@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight) {
setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
步驟 2:更新 SleepNightAdapter
- 開啟
SleepNightAdapter.kt
。 - 刪除
bind()
方法中的所有內容,因為您現在可以使用資料繫結和新的轉接程式執行這項作業。
fun bind(item: SleepNight) {
}
- 在
bind()
內,請將睡眠指派給item
,因為您需要對新的SleepNight
設定繫結物件。
binding.sleep = item
- 在該行下方新增
binding.executePendingBindings()
。這項呼叫是一項最佳化要求,可要求資料繫結立即執行任何待處理的繫結。在RecyclerView
中使用繫結轉接程式時,建議您呼叫executePendingBindings()
,因為這麼做可能會使檢視區塊的大小略微加快。
binding.executePendingBindings()
步驟 3:在 XML 版面配置中新增繫結
- 開啟
list_item_sleep_night.xml
。 - 在
ImageView
中,新增一個與屬性設定名稱相同的繫結轉接程式的app
屬性。傳入sleep
變數,如下所示。
此屬性會透過轉接程式建立視圖與繫結物件之間的連結。只要參照sleepImage
,轉接程式就會調整SleepNight
中的資料。
app:sleepImage="@{sleep}"
- 針對
sleep_length
和quality_string
文字檢視執行相同步驟。只要參照sleepDurationFormatted
或sleepQualityString
,轉接程式就會調整SleepNight
中的資料。
app:sleepDurationFormatted="@{sleep}"
app:sleepQualityString="@{sleep}"
- 執行您的應用程式,運作方式與之前相同。繫結轉接程式會處理資料格式的所有變更,並在資料變更時更新檢視表,以簡化
ViewHolder
並使程式碼的結構比以往更好。
您最近的運動清單已被列出。由此可見,「Adapter
」介面可讓您以多種方式建立程式碼架構。程式碼越複雜,建立架構時就越重要。在正式版應用程式中,這些模式和其他功能會搭配 RecyclerView
使用。所有模式都適用。請依據您所建構的項目進行選擇。
恭喜!到目前為止,您已順利使用 Android 裝置中的 RecyclerView
。
Android Studio 專案:RecyclerViewDiffUtilDatabinding。
DiffUtil
:
RecyclerView
有一個名為DiffUtil
的類別,用於計算兩份清單的差異。- 「
DiffUtil
」有一個名為「ItemCallBack
」的類別,其用來擴展兩份清單的差異。 - 在
ItemCallback
類別中,您必須覆寫areItemsTheSame()
和areContentsTheSame()
方法。
ListAdapter
:
- 如要免費管理部分名單,您可以使用
ListAdapter
類別,不要使用RecyclerView.Adapter
。不過,如果您使用ListAdapter
,就必須自行撰寫適合其他版面配置的轉接程式,因此這個程式碼研究室將示範如何進行這項操作。 - 如要在 Android Studio 中開啟意圖選單,請將遊標放到任何程式碼上,然後按下
Alt+Enter
(Mac 上為Option+Enter
)。這個選單特別適合用來重構程式碼及建立實作方法。這個選單會區分大小寫,因此您必須將遊標放到精確位置,才能看到正確的選單。
資料繫結:
- 在項目版面配置中使用資料繫結,將資料繫結至檢視模式。
繫結轉接器:
- 您先前曾使用
Transformations
從資料建立字串。如果您需要繫結不同類型或複雜類型的資料,請提供繫結轉接程式,來協助繫結資料。 - 如要宣告繫結轉接程式,請定義用來擷取項目和視圖的方法,並使用
@BindingAdapter
為方法加上註解。在 Kotlin 中,您可以將繫結轉接程式寫入為View
的擴充功能函式。傳遞轉接器所調整屬性的名稱。例如:
@BindingAdapter("sleepDurationFormatted")
- 在 XML 版面配置中,設定與繫結轉接程式名稱相同的
app
屬性。傳送包含資料的變數。例如:
.app:sleepDurationFormatted="@{sleep}"
大學課程:
Android 開發人員說明文件:
其他資源:
這個部分會列出在代碼研究室中,受老師主導的課程作業的可能學生作業。由老師自行決定要執行下列動作:
- 視需要指派家庭作業。
- 告知學生如何提交家庭作業。
- 批改家庭作業。
老師可視需要使用這些建議,並視情況指派其他合適的家庭作業。
如果您是自行操作本程式碼研究室,歡迎透過這些家庭作業來測試自己的知識。
回答這些問題
第 1 題
以下何者是使用 DiffUtil
的?請選取所有適用選項。
▢ 擴充 ItemCallBack
類別。
▢ 覆寫 areItemsTheSame()
。
▢ 覆寫 areContentsTheSame()
。
▢ 使用資料繫結來追蹤項目之間的差異。
問題 2
關於繫結轉接器,下列敘述何者正確?
▢ 繫結轉接程式是加上 @BindingAdapter
註解的函式。
▢ 使用繫結轉接程式可將資料格式與檢視畫面的分隔符號分開。
▢ 如要使用繫結轉接程式,您必須使用 RecyclerViewAdapter
。
▢ 需要轉換複雜的資料時,繫結轉接器是很好的解決方案。
問題 3
何時該使用 Transformations
取代繫結轉接器?請選取所有適用選項。
▢ 資料十分簡單。
▢ 您正在設定字串格式。
▢ 清單過長。
▢ 您的「ViewHolder
」中只有一個資料檢視。
開始下一門課程: