目的是 实现 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, 并调整布局
其中宽高都设置为 0dp, 可以使得它占满所在的位置.
2. 在XML 中, 为 RecylcerView 添加一个 布局管理器 , 例如 LinearLayoutManager
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
3. 创建列表项布局 和 文本 ViewHolder
RecyclerView 只是一个容器,
需要
(1)创建 RecyclerView 中显示的项的布局和基础架构。
(2)ViewHolder - 缓存项视图.
简单实现:
(1) text_item_view.xml 仅包含一个TextView
(2) 创建一个简单的 TextItemViewHolder 继承自 RecyclerView.ViewHolder
4. 创建适配器 SleepNightAdapter
实现 RecyclerView 时,核心任务就是创建适配器。
项视图(ItemView)有一个简单的 ViewHolder,并且每个项都有一个布局(item_view_layout.xml)。
适配器会创建一个ViewHolder,并在其中填充数据以供 RecyclerView 显示.
4.1 定义数据**
例如
var data = listOf()
延伸, 需要在data 发生变更时 通知 RecyclerView, 因为 RecyclerView 对数据一无所知, 它只了解提供的ViewHolder.
因此,可以对 data 添加 setter 中(该代码块在 SleepNightAdapter 顶部)
即:
var data = listOf()
set(value) {
field = value
notifyDataSetChanged()
}
注意:调用 notifyDataSetChanged() 后,RecyclerView 会重新绘制整个列表,而不只是已更改的项。
4.2 实现三个函数**
(1)getItemCount 获取显示的数据项数目
override fun getItemCount(): Int {
return data.size
}
(2)onCreateViewHolder 创建 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 参数(即用于容纳 ViewHolder 的视图组);
这里需要使用 parent 获取 布局加载器, 通过它加载 项布局
(3)onBindViewHolder 为ViewHolder绑定数据
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
val item = data[position]
holder.textView.text = item.sleepQuality.toString()// 仅显示 睡眠质量数值
}
5. 为 RecyclerView 设置 适配器.
先创建适配器, 然后将 其设置给 RecyclerView.
val adapter = SleepNightAdapter()
binding.sleepList.adapter = adapter
6. 将数据获取到适配器中
监听ViewModel 中的数据变化(LiveData) 并 设置到 适配器中.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
至此,可以简单的显示 睡眠质量 的 评分数据, 未包含所有数据
6. 显示所有睡眠数据
包括 睡眠质量图片、睡眠事件、睡眠质量感受等,
这里需要修改 每一项的布局文件 、重新定义ViewHolder 、对应修改创建 和绑定ViewHolder.
7. 优化代码
这部分是把 onBindViewHolder 和 onCreateViewHolder 中,和ViewHolder 相关的代码,
都移到 ViewHolder 中。
因为 ViewHolder 的实现可能是 经常变化的(例如显示 每一项的内容变更了)
7.1 onBindViewHolder 简化逻辑(交给 ViewHolder)
把加载 每一项布局 的 子View 的操作(作为成员变量XXX) 放在 ViewHolder 里,
并且 定义一个 bind()函数,它的参数就是数据,用它来更新子View.
然后 onBindViewHolder 里用 holder.bind(xxx) 就行了.
例如:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
holder.bind(item)
}
这样使得,显示 和 管理 ViewHolder 的代码分离.
7.2 onCreateViewHolder 简化逻辑(交给ViewHolder)
同样, 这里 加载 每一项布局 以及 创建 ViewHolder 的操作, 和 Adapter 关系不大,和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)
}
所以,同样可以 移动到 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 的函数内容,抽取为
from 函数, 并 在 伴生对象 companion object 里.
(2) 构造函数标记 为 private constructor,
表示其它地方无法调用构造函数创建.
(3) onCreateViewHolder 里的代码就会非常简洁:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
从这两个优化可以看出, 把属于ViewHolder 的代码集中在一起,使得 Adapter 代码 更加简洁,
ViewHolder 的变化即不同显示需求的实现变化,只需更改 ViewHolder 内部代码即可。
这是非常巧妙的设计 (抽离变化, 高内聚)
完整代码参考:
https://github.com/google-developer-training/android-kotlin-fundamentals-apps/tree/master/RecyclerViewFundamentals
--- End ---