RecyclerView(2) - 睡眠质量 示例笔记

目的是 实现 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<SleepNight>()

延伸, 需要在data 发生变更时 通知 RecyclerView, 因为 RecyclerView 对数据一无所知, 它只了解提供的ViewHolder.
因此,可以对 data 添加 setter 中(该代码块在 SleepNightAdapter 顶部)

即:

    var data = listOf<SleepNight>()
        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 ---

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容