You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,这会触发整个行的销毁重建,导致闪烁。改为复用现有行,仅更新内容项:

  1. 重构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) }
}
  1. 确保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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 18:05:22