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

RecyclerView多视图类型ListAdapter切换数据崩溃及显示异常问题

问题分析与解决方案

核心问题根源

你遇到的崩溃本质是数据源与视图位置的映射逻辑和ListAdapter的DiffUtil机制不兼容:

  • 你让单个RecommendationRecipe对应2个视图位置(日期项+食谱项),getItemCount返回数据源size*2,但ListAdapter的DiffUtil是基于数据源本身的item做差异对比,而非视图位置。
  • 切换数据集时,新旧数据源的size变化会导致视图位置与数据源索引的映射关系混乱,DiffUtil触发的更新操作会访问超出新数据源边界的索引,最终抛出IndexOutOfBoundsException。

可靠解决方案:重构数据结构(推荐)

放弃“单个数据源item对应多个视图位置”的设计,把日期项和食谱项拆成独立的数据单元,让视图位置与数据源item一一对应。

步骤1:定义统一列表项密封类

sealed class RecipeListItem {
    // 日期头部项
    data class DateHeader(val dateText: String) : RecipeListItem()
    // 食谱内容项
    data class RecipeContent(val recipe: RecommendationRecipe) : RecipeListItem()
}

步骤2:构建数据源列表

切换早餐/午餐/晚餐数据集时,将原始List<RecommendationRecipe>转换为List<RecipeListItem>:

fun convertToDisplayList(recipes: List<RecommendationRecipe>): List<RecipeListItem> {
    val displayList = mutableListOf<RecipeListItem>()
    recipes.forEach { recipe ->
        displayList.add(RecipeListItem.DateHeader(recipe.date))
        displayList.add(RecipeListItem.RecipeContent(recipe))
    }
    return displayList
}

步骤3:修改ListAdapter逻辑

  • getItemCount直接返回currentList.size
  • getItemViewType根据RecipeListItem的类型判断
  • onBindViewHolder根据item类型绑定不同视图
override fun getItemCount(): Int = currentList.size

override fun getItemViewType(position: Int): Int {
    return when(currentList[position]) {
        is RecipeListItem.DateHeader -> VIEW_TYPE_DATE
        is RecipeListItem.RecipeContent -> VIEW_TYPE_RECIPE
    }
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    when(val item = currentList[position]) {
        is RecipeListItem.DateHeader -> (holder as DateViewHolder).bind(item.dateText)
        is RecipeListItem.RecipeContent -> (holder as RecipeViewHolder).bind(item.recipe)
    }
}

这种方式下,DiffUtil可以精准对比每个独立的列表项,切换数据集时不会出现位置映射混乱,彻底避免越界崩溃,同时视图逻辑更清晰。


临时修复方案(不推荐,仅作应急)

如果暂时不想重构数据结构,必须在所有涉及位置的逻辑中添加防御性检查,并确保索引映射的正确性:

  1. 在onBindViewHolder中先校验数据源索引是否合法:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val dataIndex = position / 2
    // 防御性检查,避免越界
    if (dataIndex >= currentList.size) return
    
    val recipe = currentList[dataIndex]
    when(position % 2) {
        0 -> (holder as DateViewHolder).bind(recipe.date)
        1 -> (holder as RecipeViewHolder).bind(recipe)
    }
}
  1. 确保submitList时传入的新数据集是完整且非空的,避免DiffUtil计算时出现异常。

但这种方式只是治标不治本,后续仍可能因视图刷新逻辑复杂出现其他问题。

内容的提问来源于stack exchange,提问作者mediarahan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 12:52:08