如何在Android中利用RecyclerView动态构建嵌套树形视图结构?
Android RecyclerView树形视图实现方案(子项数量未知场景)
当然可以用单一布局配合统一的适配器实现,甚至不需要嵌套RecyclerView(嵌套反而容易引发滚动冲突、性能冗余问题),下面给你两种实用方案:
方案1:扁平列表模拟树形(推荐)
核心思路是把递归的树形数据转换成扁平的一维列表,通过缩进和展开/折叠操作动态维护列表内容,整个RecyclerView只用一个适配器。
1. 定义统一数据模型
创建一个支持递归的节点类,包含层级、子项、展开状态:
data class TreeNode( val id: String, val content: String, val level: Int, // 层级,用于设置缩进 var isExpanded: Boolean = false, val children: MutableList<TreeNode> = mutableListOf() )
2. 单一列表项布局
布局文件item_tree_node.xml里不需要嵌套RecyclerView,只需要内容区、展开按钮:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingVertical="8dp"> <!-- 缩进占位,根据层级动态设置宽度 --> <View android:id="@+id/v_indent" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"/> <!-- 展开/折叠按钮 --> <ImageView android:id="@+id/iv_expand" android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/ic_expand_more"/> <!-- 内容文本 --> <TextView android:id="@+id/tv_content" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="8" android:paddingHorizontal="8dp"/> </LinearLayout>
3. 适配器实现(核心逻辑)
适配器里维护一个扁平列表,处理展开/折叠时的列表更新:
class TreeAdapter(private val flatNodeList: MutableList<TreeNode>) : RecyclerView.Adapter<TreeAdapter.TreeViewHolder>() { inner class TreeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val indentView: View = itemView.findViewById(R.id.v_indent) val expandIv: ImageView = itemView.findViewById(R.id.iv_expand) val contentTv: TextView = itemView.findViewById(R.id.tv_content) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TreeViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_tree_node, parent, false) return TreeViewHolder(view) } override fun onBindViewHolder(holder: TreeViewHolder, position: Int) { val node = flatNodeList[position] // 根据层级设置缩进宽度(比如每层缩进24dp) val indentWidth = node.level * 24 * holder.itemView.resources.displayMetrics.density holder.indentView.layoutParams.width = indentWidth.toInt() // 设置内容 holder.contentTv.text = node.content // 控制展开按钮显示:没有子项则隐藏 holder.expandIv.visibility = if (node.children.isEmpty()) View.GONE else View.VISIBLE // 设置按钮图标 holder.expandIv.setImageResource( if (node.isExpanded) R.drawable.ic_expand_less else R.drawable.ic_expand_more ) // 处理展开/折叠点击 holder.expandIv.setOnClickListener { node.isExpanded = !node.isExpanded if (node.isExpanded) { // 展开:将子项插入到当前项的下一个位置 val insertPosition = position + 1 flatNodeList.addAll(insertPosition, node.children) notifyItemRangeInserted(insertPosition, node.children.size) } else { // 折叠:递归移除所有子项(包括子项的子项) var removeCount = 0 val iterator = flatNodeList.listIterator(position + 1) while (iterator.hasNext()) { val childNode = iterator.next() if (childNode.level <= node.level) break removeCount++ iterator.remove() } notifyItemRangeRemoved(position + 1, removeCount) } // 更新当前项图标 notifyItemChanged(position) } } override fun getItemCount() = flatNodeList.size }
4. 初始化扁平列表
把接口返回的树形数据转换成初始扁平列表(只显示一级节点):
fun convertTreeToFlatList(rootNodes: List<TreeNode>): MutableList<TreeNode> { val flatList = mutableListOf<TreeNode>() rootNodes.forEach { root -> flatList.add(root) // 如果需要默认展开某些节点,这里可以递归添加子项 } return flatList }
方案2:嵌套RecyclerView(兼容原有思路)
如果坚持用嵌套RecyclerView的方式,也可以用单一布局动态配置:
1. 修改列表项布局
在item_tree_node.xml里添加子RecyclerView,默认隐藏:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- 父项内容区(和方案1一致) --> <LinearLayout android:id="@+id/ll_parent_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingVertical="8dp"> <!-- 缩进、展开按钮、内容文本 --> </LinearLayout> <!-- 子RecyclerView,默认GONE --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_children" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" android:layout_marginLeft="24dp"/> <!-- 子项缩进 --> </LinearLayout>
2. 适配器实现
每个ViewHolder持有子RecyclerView的适配器,动态绑定子项:
class NestedTreeAdapter(private val nodeList: List<TreeNode>) : RecyclerView.Adapter<NestedTreeAdapter.NestedTreeViewHolder>() { inner class NestedTreeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val parentContentLl: LinearLayout = itemView.findViewById(R.id.ll_parent_content) val childrenRv: RecyclerView = itemView.findViewById(R.id.rv_children) val expandIv: ImageView = itemView.findViewById(R.id.iv_expand) val contentTv: TextView = itemView.findViewById(R.id.tv_content) init { // 子RecyclerView禁用嵌套滚动,避免滚动冲突 childrenRv.isNestedScrollingEnabled = false childrenRv.layoutManager = LinearLayoutManager(itemView.context) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NestedTreeViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_tree_node, parent, false) return NestedTreeViewHolder(view) } override fun onBindViewHolder(holder: NestedTreeViewHolder, position: Int) { val node = nodeList[position] holder.contentTv.text = node.content holder.expandIv.visibility = if (node.children.isEmpty()) View.GONE else View.VISIBLE holder.expandIv.setImageResource( if (node.isExpanded) R.drawable.ic_expand_less else R.drawable.ic_expand_more ) // 处理展开/折叠 holder.expandIv.setOnClickListener { node.isExpanded = !node.isExpanded holder.childrenRv.visibility = if (node.isExpanded) View.VISIBLE else View.GONE if (node.isExpanded) { // 给子RecyclerView设置适配器(递归使用同一个适配器) holder.childrenRv.adapter = NestedTreeAdapter(node.children) } notifyItemChanged(position) } // 初始化时如果节点是展开状态,显示子RecyclerView if (node.isExpanded) { holder.childrenRv.visibility = View.VISIBLE holder.childrenRv.adapter = NestedTreeAdapter(node.children) } else { holder.childrenRv.visibility = View.GONE } } override fun getItemCount() = nodeList.size }
注意事项
- 方案1的扁平列表方式性能更优,避免了多层嵌套RecyclerView的布局开销
- 如果树形结构层级很深,方案1需要注意递归处理子项的展开/折叠逻辑,避免遗漏
- 两种方案都可以完全适配接口返回的未知子项数量,不用手动设置多个适配器
内容的提问来源于stack exchange,提问作者Adithya Raj
相关产品推荐
相关产品推荐

