目的是 實(shí)現(xiàn) RecyclerView 和適配器
參考指南 和 示例代碼:
https://developer.android.com/codelabs/kotlin-android-training-recyclerview-fundamentals?index=..%2F..android-kotlin-fundamentals&hl=zh-cn#3
https://github.com/google-developer-training/android-kotlin-fundamentals-starter-apps/tree/master/RecyclerViewFundamentals-Starter
1. 布局中, 使用 RecylcerView 取代 ScrollView, 并調(diào)整布局
其中寬高都設(shè)置為 0dp, 可以使得它占滿所在的位置.
2. 在XML 中, 為 RecylcerView 添加一個(gè) 布局管理器 , 例如 LinearLayoutManager
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
3. 創(chuàng)建列表項(xiàng)布局 和 文本 ViewHolder
RecyclerView 只是一個(gè)容器,
需要
(1)創(chuàng)建 RecyclerView 中顯示的項(xiàng)的布局和基礎(chǔ)架構(gòu)。
(2)ViewHolder - 緩存項(xiàng)視圖.
簡(jiǎn)單實(shí)現(xiàn):
(1) text_item_view.xml 僅包含一個(gè)TextView
(2) 創(chuàng)建一個(gè)簡(jiǎn)單的 TextItemViewHolder 繼承自 RecyclerView.ViewHolder
4. 創(chuàng)建適配器 SleepNightAdapter
實(shí)現(xiàn) RecyclerView 時(shí),核心任務(wù)就是創(chuàng)建適配器。
項(xiàng)視圖(ItemView)有一個(gè)簡(jiǎn)單的 ViewHolder,并且每個(gè)項(xiàng)都有一個(gè)布局(item_view_layout.xml)。
適配器會(huì)創(chuàng)建一個(gè)ViewHolder,并在其中填充數(shù)據(jù)以供 RecyclerView 顯示.
4.1 定義數(shù)據(jù)**
例如
var data = listOf<SleepNight>()
延伸, 需要在data 發(fā)生變更時(shí) 通知 RecyclerView, 因?yàn)?RecyclerView 對(duì)數(shù)據(jù)一無(wú)所知, 它只了解提供的ViewHolder.
因此,可以對(duì) data 添加 setter 中(該代碼塊在 SleepNightAdapter 頂部)
即:
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}
注意:調(diào)用 notifyDataSetChanged() 后,RecyclerView 會(huì)重新繪制整個(gè)列表,而不只是已更改的項(xiàng)。
4.2 實(shí)現(xiàn)三個(gè)函數(shù)**
(1)getItemCount 獲取顯示的數(shù)據(jù)項(xiàng)數(shù)目
override fun getItemCount(): Int {
return data.size
}
(2)onCreateViewHolder 創(chuàng)建 ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.text_item_view, parent,false) as TextView
return TextItemViewHolder(view)
}
parent 參數(shù)(即用于容納 ViewHolder 的視圖組);
這里需要使用 parent 獲取 布局加載器, 通過(guò)它加載 項(xiàng)布局
(3)onBindViewHolder 為ViewHolder綁定數(shù)據(jù)
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
val item = data[position]
holder.textView.text = item.sleepQuality.toString()// 僅顯示 睡眠質(zhì)量數(shù)值
}
5. 為 RecyclerView 設(shè)置 適配器.
先創(chuàng)建適配器, 然后將 其設(shè)置給 RecyclerView.
val adapter = SleepNightAdapter()
binding.sleepList.adapter = adapter
6. 將數(shù)據(jù)獲取到適配器中
監(jiān)聽ViewModel 中的數(shù)據(jù)變化(LiveData) 并 設(shè)置到 適配器中.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
至此,可以簡(jiǎn)單的顯示 睡眠質(zhì)量 的 評(píng)分?jǐn)?shù)據(jù), 未包含所有數(shù)據(jù)
6. 顯示所有睡眠數(shù)據(jù)
包括 睡眠質(zhì)量圖片、睡眠事件、睡眠質(zhì)量感受等,
這里需要修改 每一項(xiàng)的布局文件 、重新定義ViewHolder 、對(duì)應(yīng)修改創(chuàng)建 和綁定ViewHolder.
7. 優(yōu)化代碼
這部分是把 onBindViewHolder 和 onCreateViewHolder 中,和ViewHolder 相關(guān)的代碼,
都移到 ViewHolder 中。
因?yàn)?ViewHolder 的實(shí)現(xiàn)可能是 經(jīng)常變化的(例如顯示 每一項(xiàng)的內(nèi)容變更了)
7.1 onBindViewHolder 簡(jiǎn)化邏輯(交給 ViewHolder)
把加載 每一項(xiàng)布局 的 子View 的操作(作為成員變量XXX) 放在 ViewHolder 里,
并且 定義一個(gè) bind()函數(shù),它的參數(shù)就是數(shù)據(jù),用它來(lái)更新子View.
然后 onBindViewHolder 里用 holder.bind(xxx) 就行了.
例如:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
holder.bind(item)
}
這樣使得,顯示 和 管理 ViewHolder 的代碼分離.
7.2 onCreateViewHolder 簡(jiǎn)化邏輯(交給ViewHolder)
同樣, 這里 加載 每一項(xiàng)布局 以及 創(chuàng)建 ViewHolder 的操作, 和 Adapter 關(guān)系不大,和ViewHolder 緊密相連。
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater =
LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night,
parent, false)
return ViewHolder(view)
}
所以,同樣可以 移動(dòng)到 ViewHolder里.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
....
companion object{
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.list_item_sleep_night, parent, false)
return ViewHolder(view)
}
}
分析:
(1) 將 onCreateViewHolder 的函數(shù)內(nèi)容,抽取為
from 函數(shù), 并 在 伴生對(duì)象 companion object 里.
(2) 構(gòu)造函數(shù)標(biāo)記 為 private constructor,
表示其它地方無(wú)法調(diào)用構(gòu)造函數(shù)創(chuàng)建.
(3) onCreateViewHolder 里的代碼就會(huì)非常簡(jiǎn)潔:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
從這兩個(gè)優(yōu)化可以看出, 把屬于ViewHolder 的代碼集中在一起,使得 Adapter 代碼 更加簡(jiǎn)潔,
ViewHolder 的變化即不同顯示需求的實(shí)現(xiàn)變化,只需更改 ViewHolder 內(nèi)部代碼即可。
這是非常巧妙的設(shè)計(jì) (抽離變化, 高內(nèi)聚)
完整代碼參考:
--- End ---