在RecyclerView中嵌套RecyclerView是否为最佳实践?(含父子列表场景)
最优方案分析:父子展开折叠列表实现
原嵌套RecyclerView方案的问题
你计划的嵌套RecyclerView方案确实能实现需求,但存在几个明显不足:
- 布局层级冗余:嵌套RecyclerView会增加View树层级,带来额外的绘制、测量性能开销。
- ViewHolder复用隐患:若每次父Adapter的
onBindViewHolder都初始化子RecyclerView的LayoutManager和Adapter,会造成不必要的对象重复创建;且如果没做好状态缓存,父ViewHolder复用时容易丢失子项的展开状态。 - 状态维护复杂:需要单独维护每个父项的展开状态,还要确保子RecyclerView的状态在复用后能正确恢复。
更优方案:单一RecyclerView + 多类型Item
这是Android实现展开折叠父子列表的最佳实践,通过将父项和子项定义为RecyclerView的不同类型Item,避免嵌套,简化逻辑同时提升性能。
具体实现步骤
- 定义统一的列表Item类型
用密封类封装父项和子项,同时携带展开状态、父索引等关键信息:
sealed class ListItem { // 父项:携带原始Parent数据、展开状态、自身索引 data class ParentItem(val parent: Parent, val isExpanded: Boolean, val parentIndex: Int) : ListItem() // 子项:携带原始Child数据、所属父项的索引 data class ChildItem(val child: Child, val parentIndex: Int) : ListItem() }
- 构建动态数据源
维护一个展开状态列表,根据状态生成最终展示的列表:
// 全局维护展开状态 private val expandStates = mutableListOf<Boolean>() fun buildDisplayList(parentList: List<Parent>): List<ListItem> { val displayList = mutableListOf<ListItem>() parentList.forEachIndexed { parentIndex, parent -> // 添加父项 displayList.add(ListItem.ParentItem(parent, expandStates[parentIndex], parentIndex)) // 若展开则添加所有子项 if (expandStates[parentIndex]) { parent.childs.forEach { child -> displayList.add(ListItem.ChildItem(child, parentIndex)) } } } return displayList }
- 实现多类型Adapter
重写getItemViewType区分父子项类型,创建对应ViewHolder并绑定逻辑:
class ExpandableListAdapter( private val parentList: List<Parent>, private val onChildClick: (parentIndex: Int, childIndex: Int) -> Unit ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { companion object { private const val VIEW_TYPE_PARENT = 0 private const val VIEW_TYPE_CHILD = 1 } private var displayList: List<ListItem> = emptyList() init { expandStates.addAll(List(parentList.size) { false }) displayList = buildDisplayList(parentList) } override fun getItemViewType(position: Int): Int { return when (displayList[position]) { is ListItem.ParentItem -> VIEW_TYPE_PARENT is ListItem.ChildItem -> VIEW_TYPE_CHILD } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { VIEW_TYPE_PARENT -> { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_parent, parent, false) ParentViewHolder(view) } else -> { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_child, parent, false) ChildViewHolder(view) } } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (val item = displayList[position]) { is ListItem.ParentItem -> { (holder as ParentViewHolder).bind(item) { // 切换展开状态 expandStates[item.parentIndex] = !expandStates[item.parentIndex] val newDisplayList = buildDisplayList(parentList) val startPos = position + 1 val childCount = item.parent.childs.size // 局部刷新,避免全量更新 if (expandStates[item.parentIndex]) { displayList = newDisplayList notifyItemRangeInserted(startPos, childCount) } else { displayList = newDisplayList notifyItemRangeRemoved(startPos, childCount) } } } is ListItem.ChildItem -> { (holder as ChildViewHolder).bind(item) { // 计算子项索引并回调 val childIndex = parentList[item.parentIndex].childs.indexOf(item.child) onChildClick(item.parentIndex, childIndex) } } } } override fun getItemCount(): Int = displayList.size // 父项ViewHolder:绑定布局、处理点击切换展开状态 inner class ParentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(item: ListItem.ParentItem, onToggle: () -> Unit) { itemView.tv_title.text = item.parent.title // 设置展开/折叠图标等UI itemView.setOnClickListener { onToggle() } } } // 子项ViewHolder:绑定布局、处理点击回调 inner class ChildViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(item: ListItem.ChildItem, onClick: () -> Unit) { itemView.tv_title.text = item.child.title itemView.setOnClickListener { onClick() } } } }
- 状态与回调逻辑
- 展开/折叠状态通过
expandStates统一维护,切换时仅局部刷新列表,性能更优。 - 子项点击时,通过
ChildItem携带的parentIndex结合原始父项数据,可直接获取子项索引并回调。
两种方案的适用场景
- 嵌套RecyclerView:仅适合父子项业务逻辑完全独立、子项需要复杂滚动交互(如横向滚动)的场景;若只是垂直固定高度的子项,该方案性价比极低。
- 单一RecyclerView多类型:几乎适用于所有展开折叠父子列表场景,布局层级更浅、性能更优,状态维护更简单,是Android官方推荐的实现方式。
若你坚持使用嵌套RecyclerView,可做以下优化:
- 在父ViewHolder中缓存子Adapter和LayoutManager,避免每次
onBindViewHolder重复创建。 - 用单独列表保存父项展开状态,
onBindViewHolder时根据状态设置子RecyclerView可见性。 - 给子RecyclerView设置
childRecyclerView.setNestedScrollingEnabled(false),避免嵌套滚动冲突。
内容的提问来源于stack exchange,提问作者JexSrs
相关产品推荐
相关产品推荐

