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

Android RecyclerView多视图类型:分类与条目未正确关联显示异常

问题:RecyclerView多视图类型分类与条目未正确映射

问题现象

当前Adapter先展示所有分类,再集中展示所有条目,分类与条目未正确关联,无法实现每个分类下紧跟对应条目的效果。

当前显示效果

Category 1
Category 2
Category 3
    - item
    - item
    - item
    - item
    - item
    - item

期望显示效果

Category 1
    - item
    - item
Category 2
    - item
    - item
    - item
Category 3
    - item

现有代码

Adapter代码

class MyProfileAdapter(private val context: Context, private val list: List<HelpResourceModel>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    companion object {
        const val VIEW_TYPE_RESOURCE_CATEGORY = 1
        const val VIEW_TYPE_RESOURCE_ITEM = 2
    }

    private val resourceCategories = list.groupBy { it.ProfileCategory }.keys.toList()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            VIEW_TYPE_RESOURCE_CATEGORY -> {
                val binding = HelphulCategoryRowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                ProfileCategoryViewHolder(binding)
            }
            VIEW_TYPE_RESOURCE_ITEM -> {
                val binding = ItemMyProfileAdapterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                ViewHolder(binding)
            }
            else -> throw IllegalArgumentException("Invalid view type")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder.itemViewType) {
            VIEW_TYPE_RESOURCE_CATEGORY -> {
                val viewHolder = holder as ProfileCategoryViewHolder
                viewHolder.bind(resourceCategories[position])
            }
            VIEW_TYPE_RESOURCE_ITEM -> {
                val viewHolder = holder as ViewHolder
                val resource = getItemByPosition(position)
                viewHolder.bind(resource)
            }
        }
    }

    override fun getItemCount() = list.size + resourceCategories.size

    override fun getItemViewType(position: Int): Int {
        return if (isProfileCategory(position)) {
            VIEW_TYPE_RESOURCE_CATEGORY
        } else {
            VIEW_TYPE_RESOURCE_ITEM
        }
    }

    private fun isProfileCategory(position: Int): Boolean {
        return position in resourceCategories.indices
    }

    private fun getItemByPosition(position: Int): HelpResourceModel {
        return list[position - resourceCategories.size]
    }

    inner class ViewHolder(val binding: ItemMyProfileAdapterBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(model: HelpResourceModel) {
            binding.apply {
                tvTitle.text = model.title
                tvType.text = context.getString(if (model.getType() == HelpResourceTypeEnum.FAQ) R.string.faq else R.string.resource)
                tvType.background = (ContextCompat.getDrawable(context, if (model.getType() == HelpResourceTypeEnum.FAQ) R.drawable.dark_blue_30_top_plan_box else R.drawable.orange_30_top_plan_box))
                tvType.setTextColor(ContextCompat.getColor(context, if (model.getType() == HelpResourceTypeEnum.FAQ) R.color.darkBlue else R.color.orange))
            }
        }
    }

    inner class ProfileCategoryViewHolder(val binding: HelphulCategoryRowBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(ProfileCategory: String) {
            binding.category.text = ProfileCategory
        }
    }
}

HelpResourceModel代码

@Keep
@Parcelize
data class MyProfileModel(
    val title: String = "",
    val contents: String = "",
    val resourceType: String = "",
    val persona: String = "",
    val resourceCategory: String = "",
) : Parcelable {

    fun getType(): MyProfileTypeEnum {
        return when (resourceType) {
            MyProfileTypeEnum.FAQ.name -> MyProfileTypeEnum.FAQ
            MyProfileTypeEnum.ResourceArticle.name,
            MyProfileTypeEnum.RESOURCE.name -> MyProfileTypeEnum.RESOURCE
            else -> MyProfileTypeEnum.FAQ
        }
    }
}

问题原因与解决方案

问题根源

当前Adapter逻辑完全割裂了分类和条目:前resourceCategories.size个位置全部显示分类,剩余位置集中显示所有条目,自然无法实现“分类+对应条目”的顺序展示。

解决方案

重新构造混合了分类和条目的有序列表,按“分类→对应条目→下一个分类→对应条目”的顺序排列,再基于该列表实现Adapter逻辑。

步骤1:定义统一数据类型并构造混合列表

// 定义密封类统一分类和条目的数据类型
sealed class ProfileListItem {
    data class CategoryItem(val category: String) : ProfileListItem()
    data class ResourceItem(val resource: HelpResourceModel) : ProfileListItem()
}

// 在Adapter中构造混合列表(替换原来的resourceCategories)
private val mixedList: List<ProfileListItem> by lazy {
    val result = mutableListOf<ProfileListItem>()
    // 按分类分组,遍历每个分类及其对应条目
    list.groupBy { it.resourceCategory }.forEach { (category, resources) ->
        result.add(ProfileListItem.CategoryItem(category))
        resources.forEach { result.add(ProfileListItem.ResourceItem(it)) }
    }
    result
}

步骤2:重写Adapter核心方法

override fun getItemCount() = mixedList.size

override fun getItemViewType(position: Int): Int {
    return when (mixedList[position]) {
        is ProfileListItem.CategoryItem -> VIEW_TYPE_RESOURCE_CATEGORY
        is ProfileListItem.ResourceItem -> VIEW_TYPE_RESOURCE_ITEM
    }
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    when (val item = mixedList[position]) {
        is ProfileListItem.CategoryItem -> {
            (holder as ProfileCategoryViewHolder).bind(item.category)
        }
        is ProfileListItem.ResourceItem -> {
            (holder as ViewHolder).bind(item.resource)
        }
    }
}

步骤3:清理冗余代码

删除原有的resourceCategories、isProfileCategory、getItemByPosition方法,这些逻辑已被混合列表替代。

注意事项

  • 原代码中存在字段名不一致:Adapter用it.ProfileCategory,但Model定义的是resourceCategory,需统一为resourceCategory,否则分组逻辑会失效。
  • 如需保持分类顺序,可在分组时使用toSortedMap()进行排序:list.groupBy { it.resourceCategory }.toSortedMap().forEach { ... }

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 15:54:58