RecyclerView多Item类型显示异常:预期展示5行仅出现4行问题排查
问题描述
尝试在RecyclerView中实现多视图类型展示,预期结构为1个头部+5条数据行,再接1个头部+5条数据行,实际运行仅显示4条数据行。
日志输出

核心代码
Adapter代码
class DailyFragAdapter( private val activityData: (DetailActivityData) -> Unit, private val dates: List<Date>, private val list : ArrayList<TabType1> ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() /* ListAdapter<TabType1, RecyclerView.ViewHolder>(Diff())*/ { private lateinit var context: Context private val HEADER = 1 private val ROW = 2 /*private class Diff : DiffUtil.ItemCallback<TabType1>() { override fun areItemsTheSame(oldItem: TabType1, newItem: TabType1): Boolean { return oldItem.header == newItem.header && oldItem.cps.cps[0].id == newItem.cps.cps[0].id } override fun areContentsTheSame( oldItem: TabType1, newItem: TabType1 ): Boolean { return oldItem.hashCode() == newItem.hashCode() } }*/ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { context = parent.context val layoutInflater = LayoutInflater.from(context) return if (viewType == 2) { DVH(DailyFragRecyclerItemBinding.inflate(layoutInflater, parent, false)) } else { TitleHolder(DailyTableHeaderBinding.inflate(layoutInflater, parent, false)) } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val data: TabType1 = list[position] if (holder is DVH) { data.cps.cps.forEach { Timber.i("is: a row") holder.bind(it) } }else{ Timber.i("is: header") holder as TitleHolder holder.bind(data.header) } } override fun getItemViewType(position: Int): Int { val tabType1 = list[position] return if (tabType1.header.title == "") { ROW } else { HEADER } } inner class TitleHolder(binding: DailyTableHeaderBinding) : RecyclerView.ViewHolder(binding.root) { private val groupHeader: TextView = binding.title fun bind(title: Header) { groupHeader.text = title.title } } inner class DVH(binding: DailyFragRecyclerItemBinding) : RecyclerView.ViewHolder(binding.root) { private var cpname: TextView = binding.cpName private var day1: FrameLayout = binding.frameLayout private var day2: FrameLayout = binding.frameLayout2 private var day3: FrameLayout = binding.frameLayout3 private var day4: FrameLayout = binding.frameLayout4 private var dayContainer: ArrayList<FrameLayout> = ArrayList() fun bind(cpVo: CheckingPointVo) { dayContainer.addAll(arrayListOf(day1, day2, day3, day4)) cpname.apply { text = cpVo.name setOnClickListener { activityData.invoke(DetailActivityData(cpVo.id, cpVo.pcID)) } } val map = cpVo.chartEntriesMap for (i in dates.indices) { val dateID = BkpDateUtil.getDateId(dates[i]) Timber.i("dateID $dateID") var ceVo: List<ChartEntryVo>? = map[dateID] if (ceVo == null) { ceVo = ArrayList<ChartEntryVo>() ceVo.add(ChartEntryVo(0, dateID, 0L, 0L, "", false)) } val entryValue = ceVo[0].value when (cpVo.cpType) { CheckingPointType.YES_NO -> { val fl: FrameLayout = dayContainer[i] fl.setOnClickListener { openYesNoDialog(cpVo, ceVo[0]) } if (entryValue == 1L) { setIconInChartEntry(fl, R.drawable.ic_tick, cpVo) } else { setIconInChartEntry(fl, R.drawable.ic_no_entry, cpVo) } } CheckingPointType.QUANTIFIABLE -> { } CheckingPointType.COUNTABLE -> { } CheckingPointType.CATEGORY -> { } CheckingPointType.TEXT -> { } } } dayContainer.clear() } } private fun setIconInChartEntry(fl: FrameLayout, resID: Int, cpVo: CheckingPointVo) { fl.getChildAt(0).visibility = GONE val image: ImageView = fl.getChildAt(1) as ImageView image.visibility = VISIBLE image.setImageResource(resID) /*image.setOnClickListener { activityData.invoke(DetailActivityData(cpVo.id, cpVo.pcID)) }*/ } private fun openYesNoDialog(cpVo: CheckingPointVo, ce: ChartEntryVo) { val builder = AlertDialog.Builder(context) val dataSourceArray = BkpUiUtil.getYesNoArray() val date = getDateFromId(ce.dateKey) val title: String = getDayText(date, 1) + " - " + cpVo.name builder.setTitle(title) builder.setSingleChoiceItems( dataSourceArray, BkpUiUtil.getYesNoIndex(ce.value.toString()) ) { dialog, which -> ce.value = BkpUiUtil.getYesNoId(which) activityData.invoke( DetailActivityData( cpvo = cpVo, cevo = ce,updateChart = true )) dialog.dismiss() } val d = builder.create() d.show() } override fun getItemCount() = list.size } data class DetailActivityData(var cpID: Long = 0, var pcID: Long = 0, var cpvo:CheckingPointVo = CheckingPointVo(), var cevo:ChartEntryVo= ChartEntryVo(), var updateChart : Boolean = false) data class TabType1( val header: Header = Header(""), val cps: CheckingPointVoList = CheckingPointVoList( cps = listOf( CheckingPointVo() ) ) )
数据赋值代码
val data: ArrayList<TabType1> = arrayListOf( TabType1( header = Header("Group One") ), TabType1( cps = CheckingPointVoList(it) ), TabType1( header = Header("Group Two") ), TabType1( cps = CheckingPointVoList(it) ) ) if(it1.updateChart){ vm.updateChartEntry(it1.cpvo,it1.cevo) }else{ Intent(requireContext(), CpDetailActivity::class.java).apply { putExtra("cprId", it1.cpID) putExtra("pcId", it1.pcID) requireActivity().startActivity(this) } } }, arraylist,dayslist)
运行效果

问题排查与修复方案
核心问题
你错误地将整组的N条数据都塞到了同一个TabType1对象中,传给Adapter的列表只有4个元素:2个头部类型的TabType1 + 2个包含整组数据的行类型TabType1,getItemCount()返回4,所以RecyclerView只会渲染4个item。同时你在DVH的绑定逻辑中循环遍历一整组的数据给同一个ViewHolder重复赋值,最终每个行类型的item只会显示最后一次赋值的内容,导致数据展示缺失。
修复步骤
- 改造数据构造逻辑,平铺列表元素
每个头部、每条数据行都单独对应列表里的一个元素,比如两组每组1头+5行,最终传给Adapter的列表应该有12个元素:
val data: ArrayList<TabType1> = arrayListOf() // 第一组 data.add(TabType1(header = Header("Group One"))) it.forEach { cp -> // 每条数据单独生成一个TabType1,只存当前行的单条数据 data.add(TabType1(cps = CheckingPointVoList(listOf(cp)))) } // 第二组 data.add(TabType1(header = Header("Group Two"))) it.forEach { cp -> data.add(TabType1(cps = CheckingPointVoList(listOf(cp)))) }
- 修改行item的绑定逻辑,删除循环
现在每个行类型的TabType1只存单条数据,直接取第一个元素绑定即可:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val data: TabType1 = list[position] if (holder is DVH) { // 直接取单条数据绑定,不需要循环 val cp = data.cps.cps.first() Timber.i("is: a row") holder.bind(cp) }else{ Timber.i("is: header") holder as TitleHolder holder.bind(data.header) } }
- 可选优化:减少重复操作
DVH中的dayContainer可以直接在构造时初始化,不需要每次绑定都重复添加、清空元素:
inner class DVH(binding: DailyFragRecyclerItemBinding) : RecyclerView.ViewHolder(binding.root) { private var cpname: TextView = binding.cpName private var day1: FrameLayout = binding.frameLayout private var day2: FrameLayout = binding.frameLayout2 private var day3: FrameLayout = binding.frameLayout3 private var day4: FrameLayout = binding.frameLayout4 // 构造时直接初始化,不需要每次bind操作 private val dayContainer: ArrayList<FrameLayout> = arrayListOf(day1, day2, day3, day4) fun bind(cpVo: CheckingPointVo) { // 删除原有dayContainer.addAll和clear逻辑 cpname.apply { text = cpVo.name setOnClickListener { activityData.invoke(DetailActivityData(cpVo.id, cpVo.pcID)) } } // 剩余原有逻辑保持不变 } }
内容的提问来源于stack exchange,提问作者Pemba Tamang
相关产品推荐
相关产品推荐

