Android Leanback:如何在RowsSupportFragment中更新嵌套行项
解决方案:无闪烁更新Leanback嵌套行封面
一、修复内容项的DiffCallback逻辑
你的contentDiffCallback使用hashCode()判断内容是否一致存在风险:如果ContentModel未重写hashCode(),或者hashCode()未包含封面图相关字段(比如本地存储路径),当封面下载完成后,即使ContentModel的封面字段变化,areContentsTheSame也会误判为内容未变,导致Presenter不刷新。
修改为直接对比关键字段:
private val contentDiffCallback = object : DiffCallback<ContentModel>() { override fun areItemsTheSame(oldItem: ContentModel, newItem: ContentModel): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: ContentModel, newItem: ContentModel): Boolean { // 对比所有影响UI展示的字段,重点包含封面图相关字段(比如localCoverPath) return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.localCoverPath == newItem.localCoverPath } }
二、复用已有行与内部Adapter,避免整体重建
当前每次LiveData回调都重新创建所有CardListRow和内部的ArrayObjectAdapter,这会触发整个行的销毁重建,导致闪烁。改为复用现有行,仅更新内容项:
- 重构
createRows方法,添加行复用逻辑:
private fun createRows(categoryModelList: List<CategoryModel>) { val existingRows = rowsAdapter.currentItems.toMutableList() as MutableList<CardListRow> categoryModelList.forEach { categoryModel -> // 查找现有行中是否存在同ID的分类 val existingRow = existingRows.find { it.id == categoryModel.id } if (existingRow != null) { // 复用已有行的Adapter,更新内容 val contentAdapter = existingRow.cardRow as ArrayObjectAdapter contentAdapter.setItems(categoryModel.contents ?: emptyList(), contentDiffCallback) existingRows.remove(existingRow) // 标记为已处理 } else { // 新建行并添加 val newRow = createCardRow(categoryModel) rowsAdapter.add(newRow) } } // 删除已不存在的分类行 existingRows.forEach { rowsAdapter.remove(it) } }
- 确保
CardListRow能通过id匹配分类(需要给CardListRow添加分类ID的属性,或者通过关联的CategoryModel获取):
class CardListRow(header: HeaderItem, adapter: ArrayObjectAdapter, val category: CategoryModel) : Row(header) { val id get() = category.id }
三、优化Room LiveData的数据通知
确保封面图下载完成后,更新Room数据库中的ContentModel实体时,能触发LiveData的正确回调:
- 实体类中
localCoverPath字段要正确标记为可更新,更新操作需通过Room的@Update注解执行,确保数据库行变化能被LiveData捕捉。 - 避免直接修改内存中的
ContentModel对象,必须通过Room更新数据库,让LiveData自动分发新的数据快照。
四、Presenter层面确保视图刷新
在CardPresenter的onBindViewHolder方法中,确保每次绑定都重新加载封面图,不依赖过时缓存:
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any?) { val content = item as ContentModel val cardView = viewHolder.view as CardView // 清空旧的封面,重新加载 cardView.mainImageView.setImageDrawable(null) content.localCoverPath?.let { path -> Glide.with(context) .load(File(path)) .into(cardView.mainImageView) } ?: run { // 没有封面时显示占位图,同时触发下载逻辑(确保下载完成后更新Room) cardView.mainImageView.setImageResource(R.drawable.placeholder_cover) startCoverDownload(content) } }
内容的提问来源于stack exchange,提问作者Hamid Reza
相关产品推荐
相关产品推荐

