ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

B07_RecyclerView复用与DiffUtil

B07_RecyclerView复用与DiffUtil Android 基础补强 B07RecyclerView复用的 View 为什么会带着上一条的状态摘要通过收藏标记串行和标题更新不显示两个问题区分 ViewHolder 复用、完整绑定、条目身份、内容比较及不可变列表提交。标签Android、RecyclerView、DiffUtil、ListAdapter、第一行代码本文对应《第一行代码》第 3 版第 4.6 节并补强 28 天课程 D17。RecyclerView 不只是“能滚动的数组”数据、条目 View、位置以及业务 ID 是不同概念。下面围绕同一份文章数据写传统列表示例未在你的工程编译布局与依赖需配套补齐。1. 复用复用的是容器不是文章身份文章滚出屏幕后它使用的 ViewHolder 可能被拿来展示另一篇文章。这样避免为所有记录长期创建一整套控件但旧控件上的文字、可见性、选中状态也会暂时保留。绑定新数据时如果只处理“已收藏时显示图标”却没处理“未收藏时隐藏图标”第二篇就可能继承第一篇的视觉状态。因此完整绑定必须让当前 View 的显示由当前模型决定。标题、作者、图标、可见性、背景、启用状态和点击行为都需要明确赋值。图片请求也要关联当前条目并处理占位与取消不能让上一张图的异步结果覆盖新条目。复用机制由 RecyclerView 组织正确绑定是 Adapter 的职责。RecyclerView 官方指南布局管理器决定条目如何排列Adapter 决定如何创建与绑定ViewHolder 保存条目引用。把这些角色分开之后切成网格主要改变布局安排不应该迫使网络请求或收藏逻辑全部重写。2. 同一篇文章也可能需要更新DiffUtil 的身份比较回答“这是不是同一条业务记录”通常使用文章 ID内容比较回答“显示相关的数据是否相同”。同一个 ID 的标题或收藏状态变化时身份相同、内容不同应该重新绑定。如果两个比较都只比较 ID条目就会看起来不更新。以下为简化的 ListAdapter假设启用 ViewBindingitem_article.xml生成ItemArticleBinding具有title、author、bookmarkIcon。依赖 RecyclerView 与 AndroidX Core 的isVisible扩展省略导包和宿主配置。dataclassArticleRow(valid:Long,valtitle:String,valauthor:String,valbookmarked:Boolean)classArticleAdapter(privatevalonClick:(Long)-Unit):ListAdapterArticleRow,ArticleAdapter.Holder(DIFF){classHolder(privatevalbinding:ItemArticleBinding):RecyclerView.ViewHolder(binding.root){funbind(row:ArticleRow,onClick:(Long)-Unit){binding.title.textrow.title binding.author.textrow.author binding.bookmarkIcon.isVisiblerow.bookmarked binding.root.isSelectedrow.bookmarked binding.root.setOnClickListener{onClick(row.id)}}}overridefunonCreateViewHolder(parent:ViewGroup,viewType:Int):Holder{valbindingItemArticleBinding.inflate(LayoutInflater.from(parent.context),parent,false)returnHolder(binding)}overridefunonBindViewHolder(holder:Holder,position:Int){holder.bind(getItem(position),onClick)}companionobject{valDIFFobject:DiffUtil.ItemCallbackArticleRow(){overridefunareItemsTheSame(old:ArticleRow,new:ArticleRow)old.idnew.idoverridefunareContentsTheSame(old:ArticleRow,new:ArticleRow)oldnew}}}这里把点击转换为业务 ID避免长期捕获绑定时的位置。位置可能因插入、删除而变化身份则代表目标文章。如果业务需要点击瞬间的最新数据可在回调后按 ID 从当前状态查询如果读取bindingAdapterPosition必须处理NO_POSITION不能未经检查直接当数组下标。old new依赖本例数据类字段恰好覆盖显示内容。若模型还包含不影响 UI 的内部字段可以定义更精确的内容比较若把展示字段藏在未参与比较的可变对象里则需要重新设计不然比较结果可能失真。DiffUtil API3. 提交新列表也要保持旧快照不变ListAdapter 会计算列表差异并分发更新。参与比较的旧列表和元素应保持不变更新收藏时使用map与copy生成新快照再提交。只在同一个 MutableList 内把字段改掉再提交原对象可能让差异计算失去“修改前”的证据。ListAdapter API示例更新可以写成adapter.submitList(rows.map { if (it.id target) it.copy(bookmarked true) else it })。真正业务应先更新 ViewModel 或 Repository由状态观察把新列表交给 Adapter而不是 Adapter 自己保存另一套永久收藏事实。差异更新与 View 复用解决不同成本。前者描述数据变化后者减少条目对象反复创建。notifyDataSetChanged()可以要求全面刷新却不能修复错误身份、原地修改或绑定遗漏也不应作为所有问题的默认补丁。4. 两个故障实验和一个顺序检查先准备二十条文章只收藏第一条。故意让绑定只在真分支显示收藏图标再向下滚动。预期复用后可能出现不该显示的标记。恢复无条件赋值后重复滚动预期每条显示都与模型一致。记录条目 ID 与 ViewHolder 标识可以观察相同容器如何服务不同文章。再让同 ID 文章改变标题先把内容比较错误地写成只比较 ID观察新标题不及时展示恢复完整内容比较后提交新快照预期只更新应变化的条目。最后在列表首部插入一条再点击原第三篇核对回调 ID 仍对应那篇文章而不是插入后的第三个位置。以上为实验设计与预期不是已运行报告。若某次现象没有出现应通过可控滚动和日志确认是否实际发生复用不能据一次截图宣布机制不存在。5. 原创面试问答与追问问一复用为什么会导致状态串到别的条目新绑定没有覆盖旧 View 的全部相关属性。追问销毁所有 View 能解决吗可能掩盖症状但失去复用收益正确做法是完整绑定。问二身份相同是否意味着内容相同不意味着同 ID 的标题和收藏状态都可能变化。追问比较内容时漏了收藏字段会怎样收藏变化可能无法触发应有更新。问三为什么不能把 position 当长期身份插入、删除、排序会改变它。追问点击要传什么通常传业务 ID再按需要读取最新模型读取当前位置时处理无效位置。上述为原创自测可配合面试鸭 RecyclerView 主题复述未照搬题库答案。6. 专题验收能解释创建、绑定、复用、差异计算各发生了什么完成快速滚动、同 ID 更新、首部插入后的点击检查更新数据时不修改旧快照。书本第 4.6 节提供列表起点本篇补上维护真实列表最常见的状态与身份问题再与 D17 的生命周期收集连接起来。
返回列表