用RecyclerView实现N级树形列表

2023-11-16

最近在做项目的时候,需要实现一个章节树的功能。设计图大致类似这样

所谓树形列表,即是在父元素中包含子元素,当点击父元素的时候进行展开子元素,再次点击时收起子元素。且树形列表往往有多个层级。比较典型的情况就是计算机中的文件系统以及书籍中的目录这两种场景。

在我的项目场景中仅仅是展示一个三级列表,这样的话其实可以选择三个RecyclerView嵌套的方式,采用这种方式实现的优点是思考起来简单,容易理解。但是缺点是对RecyclerView需要有较多的控制,并且在项目迭代中不够灵活。

因此我认为采用一个RecyclerView实现更优雅一些,只用对一个View进行操作并且可以支持适配,理论上可以实现N级列表,只要N在计算机允许的范围内。即便这种实现方式需要对原始的数据进行处理。而且这种实现方式对于RecyclerView的特点进行了较好了利用,在展开与收起子元素的时候可以很好利用RecyclerView支持局部刷新的特点。

接下来先看一下我们的数据模型,在项目实际开发中,数据一般由服务端返回。而这种树形列表的数据模型一般是这样

data class ChapterSelectorModel(
    val id: Int,
    val name: String,
    val childlist: List<ChapterSelectorModel>?
)

 服务端返回的数据一般只会包含必要的信息,但是我们要展示与隐藏子元素的话,需要知道数据中的父子关系,这就要求我们对返回的数据进行必要的处理。比如变成这样

data class ChapterSelectorModel(
    val id: Int,
    val name: String,
    var depth: Int,
    var isExpanded: Boolean,
    val childlist: List<ChapterSelectorModel>?
)

其实就是增加必要的信息来作为父子判断的标准,我这里加入了深度与展开状态,当然你也加入是否叶子节点,是否有子孩子等等。

因为数据是从服务端获取,我们写一个全量的数据模型去接受服务端数据就可以,并不需要多写一个数据模型。

拿到之后我们需要递归的将每个元素的深度算出来并给展开状态赋值。

  private fun reSetData(chapters: List<ChapterSelectorModel>?, depth: Int) {
        if (chapters == null || chapters.isEmpty()) {
            return
        }
        for (chapter in chapters) {
            chapter.depth = depth
            chapter.isExpanded = false
            if (chapter.childlist != null && chapter.childlist.isNotEmpty()) {
                reSetData(chapter.childlist, depth + 1)
            }
        }
    }

在拿到数据的地方调用这个函数,深度初始值传入0即可。这样就会得到每个元素的深度值。

之后将新的数据传递给我们RecyclerView的Adapter即可。不过现在只是拿到了新的数据,但对于RecyclerView来讲,它只能展示一个列表的数据,即第一级列表,如何能让它实现多级列表的需求还需要我们再思考一下。

我们先讨论点击展开的情况,我们在点击第一级列表的子项时,需要展示一下该子项下的列表内容,那么是不是可以再点击的时候将该子项的列表加入父列表中就可以了。按照这个思路就很容易做到点击展开的效果了,这里也刚好有一个字段用于判断该Item是否展开。展开的代码如下

  private fun onOpen(itemData: ChapterSelectorModel, position: Int) {
        if (itemData.childlist != null && itemData.childlist.isNotEmpty()) {
            chapterSelectorData.addAll(position + 1, itemData.childlist)
            itemData.isExpanded = true
            notifyItemRangeInserted(position + 1, itemData.childlist.size)
            notifyItemChanged(position)
        }
    }

可以看到,将该元素的子元素添加到本身位置的后面,将展开状态置为true。但是对插入的条目进行局部刷新以及由于展开状态的变化对本身需要进行刷新。

然后再讨论一下折叠的情况,首先第一想法就是找到下一个和当前item深度相同的元素,用next标记,这样我们将当前item和next之间的元素从当前展示列表中删除即可。当然由于子元素也可能是展开的,因此需要递归的将子项的所有展开的列表从父列表中删除,并将子项中的展开状态置为false。折叠代码如下

    private fun onClose(itemData: ChapterSelectorModel, position: Int) {
        if (itemData.childlist != null && itemData.childlist.isNotEmpty()) {
            var next = chapterSelectorData.size - 1
            if (chapterSelectorData.size > position + 1) {
                for (i in position + 1 until chapterSelectorData.size) {
                    if (chapterSelectorData[i].depth <= chapterSelectorData[position].depth) {
                        next = i - 1
                        break
                    }
                }
                closeChild(chapterSelectorData[position])
                if (next > position) {
                    chapterSelectorData.subList(position + 1, next + 1).clear()
                    itemData.isExpanded = false
                    notifyItemRangeRemoved(position + 1, next - position)
                    notifyItemChanged(position)
                }
            }
        }
    }

 private fun closeChild(itemData: ChapterSelectorModel) {
        if (itemData.childlist != null) {
            for (child in itemData.childlist) {
                child.isExpanded = false
                closeChild(child)
            }
        }
    }

下面是完整的Adapter代码。

class ChapterTreeAdapter(
    private val context: Context,
    private val chapterSelectorData: MutableList<ChapterSelectorModel>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    inner class ViewHolder(val binding: ChapterTreeItemBinding) :
        RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val binding =
            ChapterTreeItemBinding.inflate(LayoutInflater.from(context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is (ViewHolder)) {
            val itemData = chapterSelectorData[holder.absoluteAdapterPosition]
            holder.binding.chapterItem.text = itemData.name
            if (itemData.childlist != null && itemData.childlist.isNotEmpty()) {
                holder.binding.chapterItemState.visibility = View.VISIBLE
                if (itemData.isExpanded) {
                    holder.binding.chapterItemState.setImageResource(R.drawable.list_close_icon)
                } else {
                    holder.binding.chapterItemState.setImageResource(R.drawable.list_open_icon)
                }
            } else {
                holder.binding.chapterItemState.visibility = View.INVISIBLE
            }
            val left = (itemData.depth) * 5
            holder.binding.chapterItem.setPadding(left * 12, 0, 0, 0)
            holder.itemView.setOnClickListener {
                if (itemData.isExpanded) {
                    onClose(itemData, holder.getAbsoluteAdapterPosition())
                } else {
                    onOpen(itemData, holder.getAbsoluteAdapterPosition())
                }
            }
        }
    }

    override fun getItemCount() = chapterSelectorData.size

    private fun onClose(itemData: ChapterSelectorModel, position: Int) {
        if (itemData.childlist != null && itemData.childlist.isNotEmpty()) {
            var next = chapterSelectorData.size - 1
            if (chapterSelectorData.size > position + 1) {
                for (i in position + 1 until chapterSelectorData.size) {
                    if (chapterSelectorData[i].depth <= chapterSelectorData[position].depth) {
                        next = i - 1
                        break
                    }
                }
                closeChild(chapterSelectorData[position])
                if (next > position) {
                    chapterSelectorData.subList(position + 1, next + 1).clear()
                    itemData.isExpanded = false
                    notifyItemRangeRemoved(position + 1, next - position)
                    notifyItemChanged(position)
                }
            }
        }
    }

    private fun onOpen(itemData: ChapterSelectorModel, position: Int) {
        if (itemData.childlist != null && itemData.childlist.isNotEmpty()) {
            chapterSelectorData.addAll(position + 1, itemData.childlist)
            itemData.isExpanded = true
            notifyItemRangeInserted(position + 1, itemData.childlist.size)
            notifyItemChanged(position)
        }
    }

    private fun closeChild(itemData: ChapterSelectorModel) {
        if (itemData.childlist != null) {
            for (child in itemData.childlist) {
                child.isExpanded = false
                closeChild(child)
            }
        }
    }
}

大家根据自己的实际需求添加自己的逻辑即可。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用RecyclerView实现N级树形列表 的相关文章

随机推荐

  • [从零学习汇编语言] - BX寄存器与loop指令

    文章目录 前言 一 Bx寄存器与 偏移地址 二 loop指令与jmp指令 1 jmp指令 2 loop指令 三 一些奇奇怪怪的注意点 1 汇编源程序的数字问题 2 Debug和Masm的区别 1 mov ax 0 问题 3 段前缀 四 课后
  • Postern中配置和使用Socks5代理指南

    在Postern中配置和使用Socks5代理 可以为你的爬虫项目提供更灵活 更可靠的网络连接 本文将向你分享如何在Postern中配置和使用Socks5代理的方法 解决可能遇到的问题 配置和使用Socks5代理的步骤 1 了解Socks代理
  • python程序格式_三、Python程序规范

    三 python程序规范 python的设计哲学 大道至简 优雅 明确 简洁 在交互式解释器中输入 import this 会出现python之禅 Python之禅 by Tim Peters 优美胜于丑陋 Python 以编写优美的代码为
  • 2020年数学建模国赛C题:中小微企业的信贷决策

    2020年高教社杯全国大学生数学建模竞赛题目 请先阅读 全国大学生数学建模竞赛论文格式规范 C题 中小微企业的信贷决策 在实际中 由于中小微企业规模相对较小 也缺少抵押资产 因此银行通常是依据信贷政策 企业的交易票据信息和上下游企业的影响力
  • myeclipse安装

    myeclipse2019安装 myeclipse2019下载 下载链接 https www myeclipsecn com download 这里以离线版安装为例 打开下载得到的文件 点击next 软件安装稍久 请耐心等待 软件安装完成
  • 毫米波雷达_一文读懂毫米波雷达

    汽车已进入无人驾驶探索阶段 无人驾驶的眼睛 毫米波雷达成为必不可少的一员 文 安兹 集微网 整理 Zn Lab 无人驾驶的眼睛 毫米波雷达 汽车已经进入无人驾驶探索阶段 可以主动防护汽车驾驶安全的高级驾驶辅助系统 以下简称 ADAS 技术也
  • 【博客691】VictoriaMetrics如何支持Multi Retention

    VictoriaMetrics如何支持Multi Retention 场景 实现Multi Retention Setup within VictoriaMetrics Cluster 使得为不同的监控数据采用不同的保存时间 Multi R
  • ES6箭头函数(三)-应用场景

    直接作为事件handler document addEventListener click ev gt console log ev 作为数组排序回调 var arr 1 9 2 4 3 8 sort a b gt if a b gt 0
  • 如何将视频导入到ipad中并播放

    首先在电脑上下载并安装itunes 然后用apple账号登入 在ipad上从apple store中下载一个播放器如KMPlayer 点击itunes上小手机的图标 找到文件共享 选中应用KMPlayer 然后将文件拖到右边的框里就能完成传
  • struts2漏洞攻击一例 怎样利用Struts2的漏洞(2.0.0<=version<=2.3.15)搞垮一个基于Struts2写的网站? Struts是java web framewor

    struts2漏洞攻击一例 怎样利用Struts2的漏洞 2 0 0 lt version lt 2 3 15 搞垮一个基于Struts2写的网站 Struts是java web frameworks里面的鼻祖了 现在大量的web apps
  • 微信公众号支付页面 jsapi

    1 引入 2 后端预支付完成返回相关数据之后前端操作 if typeof wx undefined wx config debug true 开启调试模式 appId datas data appId 公众号的唯一标识
  • 关于DEM土方量算的计算方法

    关于DEM土方量算的计算方法 最近在写一个关于dem土方计算的功能 网上搜索了下 普遍提到的都是三角网和矩形格网形式进行计算 然后又研究了下arcgis软件的结果 最后发现arcgis中使用的也是格网形式进行了矩形立方计算 于是开始动手编写
  • 黑马程序员Mysql

    MySQL 1 DDL操作之数据库操作 查看所有的数据库 show databases 创建数据库 CREATE database mydb1 CREATE database if not exists mydb1 选择使用哪个数据库 us
  • 【第32篇】YOLOR:多任务的统一网络

    YOLOR 多任务的统一网络 人们通过视觉 听觉 触觉以及过去的经验 理解 世界 人类经验可以通过正常学习 我们称之为显性知识 或潜意识 我们称之为隐性知识 来学习 这些通过正常学习或潜意识学习到的经验将被编码并存储在大脑中 使用这些丰富的
  • 辽宁工业大学计算机专业分数线,2019辽宁工业大学录取分数线及历年专业分数线统计表【文科 理科】...

    1 历年辽宁工业大学全国排名 校友会版 在校友会版本排名中 2018辽宁工业大学全国排名第365 相较于2017年的374名 排名上升了9位 年度全国排名省内排名总分 20183652460 68 20173742660 53 201635
  • 【Python报错-01】解决matplotlib在Pycharm中运行报错:vars()参数必须有__dict__属性

    1 运行错误 1 程序的原代码如下图所示 import matplotlib pyplot as plt 是可视化绘图工具 省略了计算SSE的代码 plt plot range 1 11 SSE plt xlabel 聚类数k fontsi
  • 转:关于Flash Player10 RTMFP的FAQ

    什么是RTMFP RTMFP 是 Real Time Media Flow Protocol的缩写 是Adobe准备推出的一种新的通信协议 这种通信协议可以让 Flash 客户端直接和另外一个Flash 客户端之间进行数据通信 也就是常说的
  • 小熊派-鸿蒙·季开发问题及解决方案

    在开发小熊派开发板时 发现给出的文档教程并不全面 由此记录下开发过程中遇到的问题 以便后人学习中可以少走弯路 也谨以此文当记录笔者自身遇到的问题 一 开发环境搭建相关问题 笔者采用小熊派创建好的linux镜像 所以省去了很多安装工具的烦恼
  • 【华为OD机试】分苹果【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 A B两个人把苹果分为两堆 A希望按照他的计算规则等分苹果 他的计算规则是按照二进制加法计算 并且不计算进位 12 5 9 1100 0101 9 B的计算规则是十进
  • 用RecyclerView实现N级树形列表

    最近在做项目的时候 需要实现一个章节树的功能 设计图大致类似这样 所谓树形列表 即是在父元素中包含子元素 当点击父元素的时候进行展开子元素 再次点击时收起子元素 且树形列表往往有多个层级 比较典型的情况就是计算机中的文件系统以及书籍中的目录