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

Android(Kotlin)嵌套RecyclerView多层级数据展示异常排查

解决Android嵌套RecyclerView数据覆盖问题及多层级数据展示方案

问题分析

核心问题在于Room异步加载房间数据时,FloorsAdapter的onBindViewHolder中未正确绑定当前楼层的请求上下文,导致后续返回的房间数据覆盖了之前楼层的子列表数据。


数据覆盖问题的解决方案

1. 用Room关联查询一次性加载层级数据(最优解)

避免分开异步请求楼层和房间,直接在Room中定义关联实体,一次性获取包含楼层、对应房间的完整数据:

// 定义关联实体
data class FloorWithRooms(
    @Embedded val floorName: String, // 替换为你的楼层实体类或字段
    @Relation(
        parentColumn = "floor_name", // 楼层表中用于关联的字段
        entityColumn = "floor_belong" // 房间表中关联楼层的字段
    )
    val rooms: List<String> // 对应房间列表,替换为你的房间实体类
)

// 在Dao中添加事务查询方法
@Dao
interface FloorDao {
    @Transaction
    @Query("SELECT * FROM floors")
    fun getFloorsWithRooms(): Flow<List<FloorWithRooms>>
}

在Fragment中观察这个Flow,一次性拿到所有层级数据:

floorDao.getFloorsWithRooms()
    .collectLatest { floorsWithRooms ->
        floorsRecyclerView.adapter = FloorsAdapter(floorsWithRooms)
    }

2. 修正嵌套RecyclerView的Adapter实现

顶层FloorsAdapter直接接收关联数据,绑定子RecyclerView时直接传入对应房间列表,无需异步请求:

class FloorsAdapter(private val data: List<FloorWithRooms>) : RecyclerView.Adapter<FloorsAdapter.FloorViewHolder>() {

    inner class FloorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvFloor: TextView = itemView.findViewById(R.id.tv_floor)
        val rvRooms: RecyclerView = itemView.findViewById(R.id.rv_rooms)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FloorViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_floor, parent, false)
        val holder = FloorViewHolder(view)
        // 初始化子RecyclerView布局管理器,仅执行一次
        holder.rvRooms.layoutManager = LinearLayoutManager(parent.context)
        return holder
    }

    override fun onBindViewHolder(holder: FloorViewHolder, position: Int) {
        val floorData = data[position]
        holder.tvFloor.text = floorData.floorName
        // 直接给子RecyclerView设置当前楼层的房间数据
        holder.rvRooms.adapter = RoomsAdapter(floorData.rooms)
    }

    override fun getItemCount() = data.size
}

3. 若必须分开展开请求的兼容方案

如果业务限制无法一次性加载关联数据,需在onBindViewHolder中管理异步请求的生命周期,避免旧数据覆盖:

// 在FloorViewHolder中添加协程Job管理
inner class FloorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val tvFloor: TextView = itemView.findViewById(R.id.tv_floor)
    val rvRooms: RecyclerView = itemView.findViewById(R.id.rv_rooms)
    var job: Job? = null // 用于取消旧请求
}

override fun onBindViewHolder(holder: FloorViewHolder, position: Int) {
    val floorName = data[position]
    holder.tvFloor.text = floorName
    // 取消之前的异步请求,防止旧数据回调覆盖当前数据
    holder.job?.cancel()
    // 发起新的房间数据请求
    holder.job = lifecycleScope.launch {
        val rooms = roomDao.getRoomsByFloor(floorName)
        holder.rvRooms.adapter = RoomsAdapter(rooms)
    }
}

多层级数据展示的最优方案及工具库

推荐方案

  1. 关联查询+嵌套RecyclerView:适合3层以内的层级数据,逻辑清晰,结合Room的关联查询可避免异步问题,开发成本低。
  2. 单一RecyclerView+多类型Item:将所有层级数据(楼层、房间、人员)扁平化,通过ItemType区分层级,避免嵌套RecyclerView的滚动性能问题,需自行实现展开/折叠逻辑。
  3. ExpandableListView:适合简单的二级展开场景,三级嵌套需额外处理,灵活性不如RecyclerView。

实用工具库

  • Epoxy:简化复杂RecyclerView布局开发,支持多层级、多类型Item,自动管理视图复用,适合复杂UI场景。
  • Groupie:专注于RecyclerView的分组和多类型Item开发,快速实现层级数据的分组展示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 14:30:24