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

Android Studio Kotlin多布局RecyclerView数据缺失问题求助

RecyclerView多布局异常排查与修复

问题现象

实现了支持三种记录类型的RecyclerView多布局展示:

  • 仅包含1型(SHIP_ROW)或同时包含1型、2型(STATEROOM_ROW)记录时,所有数据正常显示:
    仅含1型和2型记录时,所有数据正常显示
  • 当数据中存在3型(STATEROOM_PROPERTIES_ROW)记录时,该记录之后的所有1型、2型记录均无法展示,无报错信息:
    在首个2型记录后添加3型记录,后续数据无法显示

问题代码

class ShipsAdapter (private val stateroomInfo: ArrayList<StateroomAdapterData>,
                    private val listener: OnItemClickListener):
       RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var rowType = 0

    override fun getItemViewType(position: Int): Int {
       rowType = when (stateroomInfo[position].adapterRecordType) {
            Constants.SHIP_ROW -> Constants.SHIP_ROW
            Constants.STATEROOM_ROW -> Constants.STATEROOM_ROW
            else -> Constants.STATEROOM_PROPERTIES_ROW
        }
        return rowType
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
         when (rowType){
            Constants.SHIP_ROW -> {
                val view = ListShipsRecyclerItemRowBinding.inflate(LayoutInflater.from(parent.context),
                                                                            parent, false)
                ListShipsViewHolder(view)
            }
            Constants.STATEROOM_ROW -> {
                val view = ListStateroomsRecyclerItemRowBinding.inflate(LayoutInflater.from(parent.context),
                                                                                    parent, false)
                ListStateroomsViewHolder(view)
            }
            else -> {
                val view = ListStateroomPropertiesRecyclerItemRowBinding.inflate(LayoutInflater.from(parent.context),
                                                                                parent, false)
                ListStateroomPropertiesViewHolder(view)
            }
        }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (rowType) {
            Constants.SHIP_ROW -> (holder as ListShipsViewHolder).bind(position)
            Constants.STATEROOM_ROW -> (holder as ListStateroomsViewHolder).bind(position)
            Constants.STATEROOM_PROPERTIES_ROW -> (holder as ListStateroomPropertiesViewHolder).bind(position)
        }
    }

    override fun getItemCount(): Int {
        return stateroomInfo.size
    }

    inner class ListShipsViewHolder(shipsRow: ListShipsRecyclerItemRowBinding):
                                    RecyclerView.ViewHolder(shipsRow.root),
                                    View.OnClickListener {
        private val tvShip = shipsRow.tvShip
        private var ivEditShip = shipsRow.ivEditShip
        private var ivDeleteShip = shipsRow.ivDeleteShip
        private var ivStateroom = shipsRow.ivStateroom

        init {
             ivEditShip.setOnClickListener(this)
             ivDeleteShip.setOnClickListener(this)
             ivStateroom.setOnClickListener(this)
        }

        override fun onClick(v: View?) {
            if (adapterPosition != RecyclerView.NO_POSITION)
                if (v != null) {
                    when (v.id) {
                        R.id.ivEditShip -> listener.onItemClicked(adapterPosition, Constants.EDIT_SHIP_CLICKED)
                        R.id.ivDeleteShip -> listener.onItemClicked(adapterPosition, Constants.DELETE_SHIP_CLICKED)
                        R.id.ivStateroom -> listener.onItemClicked(adapterPosition, Constants.STATEROOM_CLICKED)
                    }
                }
        }

        fun bind(position: Int) {
            val shipData = stateroomInfo[position]
            tvShip.text = shipData.ship
        }
    }

    inner class ListStateroomsViewHolder(stateroomRow: ListStateroomsRecyclerItemRowBinding):
                                        RecyclerView.ViewHolder(stateroomRow.root),
                                        View.OnClickListener {
        private val tvStateroom = stateroomRow.tvStateroom
        private var ivEditStateroom = stateroomRow.ivEditStateroom
        private var ivDeleteStateroom = stateroomRow.ivDeleteStateroom

        init {
            ivEditStateroom.setOnClickListener(this)
            ivDeleteStateroom.setOnClickListener(this)
        }

        override fun onClick(v: View?) {
            if (adapterPosition != RecyclerView.NO_POSITION)
                if (v != null) {
                    when (v.id) {
                        R.id.ivEditStateroom -> listener.onItemClicked(adapterPosition, Constants.EDIT_STATEROOM_CLICKED)
                        R.id.ivDeleteStateroom -> listener.onItemClicked(adapterPosition, Constants.DELETE_STATEROOM_CLICKED)
                    }
                }
        }

        fun bind(position: Int) {
            val stateroomData = stateroomInfo[position]
            tvStateroom.text = stateroomData.stateroom
        }
    }

    inner class ListStateroomPropertiesViewHolder(stateroomPropertiesRow: ListStateroomPropertiesRecyclerItemRowBinding):
                                    RecyclerView.ViewHolder(stateroomPropertiesRow.root),
                                    View.OnClickListener {
        private val tvBedrooms = stateroomPropertiesRow.tvBedrooms
        private val tvBaths = stateroomPropertiesRow.tvBathrooms
        private val tvConnecting = stateroomPropertiesRow.tvIsConnecting
        private val ivEditStateroomProperties = stateroomPropertiesRow.ivEditStateroomProperties

        init {
            ivEditStateroomProperties.setOnClickListener(this)
        }

        fun bind(position: Int) {
            val stateroomPropertiesData = stateroomInfo[position]
            tvBedrooms.text = stateroomPropertiesData.numBedrooms
            tvBaths.text = stateroomPropertiesData.numBaths
            tvConnecting.text = stateroomPropertiesData.connectingSuite
        }

        override fun onClick(v: View?) {
            if (adapterPosition != RecyclerView.NO_POSITION)
                if (v != null) {
                   listener.onItemClicked(adapterPosition, Constants.EDIT_STATEROOM_PROPERTIES_CLICKED)
                }
        }
    }

    // 用于和Fragment的点击事件通信
    interface OnItemClickListener {
        fun onItemClicked(position: Int, clickType: Int)
    }
}

问题原因

核心问题在于全局变量rowType被错误复用:

  1. getItemViewType会为每个Item计算类型并赋值给rowType,但RecyclerView的ViewHolder创建和绑定是异步批量执行的
  2. 当3型记录出现后,rowType被固定为3型,后续onCreateViewHolder和onBindViewHolder直接使用这个全局值,而非当前Item的真实类型
  3. 导致后续原本是1型/2型的Item被错误创建为3型ViewHolder,绑定数据时类型不匹配,最终无法正常显示内容

修复方案

移除全局rowType变量,直接使用方法传入的参数或当前Item的真实类型:

1. 修改getItemViewType

直接返回计算结果,不再赋值给全局变量:

override fun getItemViewType(position: Int): Int {
    return when (stateroomInfo[position].adapterRecordType) {
        Constants.SHIP_ROW -> Constants.SHIP_ROW
        Constants.STATEROOM_ROW -> Constants.STATEROOM_ROW
        else -> Constants.STATEROOM_PROPERTIES_ROW
    }
}

2. 修改onCreateViewHolder

使用方法参数viewType判断类型:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
    when (viewType){
        Constants.SHIP_ROW -> {
            val view = ListShipsRecyclerItemRowBinding.inflate(LayoutInflater.from(parent.context),
                parent, false)
            ListShipsViewHolder(view)
        }
        Constants.STATEROOM_ROW -> {
            val view = ListStateroomsRecyclerItemRowBinding.inflate(LayoutInflater.from(parent.context),
                parent, false)
            ListStateroomsViewHolder(view)
        }
        else -> {
            val view = ListStateroomPropertiesRecyclerItemRowBinding.inflate(LayoutInflater.from(parent.context),
                parent, false)
            ListStateroomPropertiesViewHolder(view)
        }
    }

3. 修改onBindViewHolder

通过当前Item的真实类型或ViewHolder的itemViewType判断:

// 方式一:直接获取当前Item的类型
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val itemType = stateroomInfo[position].adapterRecordType
    when (itemType) {
        Constants.SHIP_ROW -> (holder as ListShipsViewHolder).bind(position)
        Constants.STATEROOM_ROW -> (holder as ListStateroomsViewHolder).bind(position)
        else -> (holder as ListStateroomPropertiesViewHolder).bind(position)
    }
}

// 方式二:使用ViewHolder的itemViewType
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    when (holder.itemViewType) {
        Constants.SHIP_ROW -> (holder as ListShipsViewHolder).bind(position)
        Constants.STATEROOM_ROW -> (holder as ListStateroomsViewHolder).bind(position)
        Constants.STATEROOM_PROPERTIES_ROW -> (holder as ListStateroomPropertiesViewHolder).bind(position)
    }
}

说明

修改后,每个Item的ViewHolder创建和数据绑定都会基于自身的真实类型,不会被之前Item的类型干扰,3型记录之后的1型、2型记录即可正常展示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 20:25:24