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
相关产品推荐
相关产品推荐

