RecyclerView实现多级树形控件,可以展开或折叠

  • A+
所属分类:Android

这里说的树形结构,指的是分父级子级元素。点击父级,可以展开或者隐藏子级,且父子一共有多级(甚至无限层级,但现实中一般没有这种情况)。现实中的场景,首先能想到的是文件管理器应用,随着目录层级一层层展开。还有一个常见的应用,就是学科-知识点或大课程包含子课程,也就是很多在线教育类APP的题库和课程表中能看到的。

先看一下效果:

AndroidTreeRecyclerViewDemo
以往要实现这种列表,除了ListView几乎没有别的选择(Google早期也没提供其他合适的控件)。使用ListView很简单,实现能展开和关闭的多级树思路也简单:展开就在相应的位置add数据,关闭就remove数据,然后notifyDataChanged。但这么做有个问题,当数据量较大时,每次点击(无论展开还是关闭)都要notifyDataChanged,这对性能的要求很高,很容易导致界面卡住不流畅甚至是ANR。这时候RecyclerView就可以派上用场了,无它,因为支持局部刷新嘛。

显然,实现多级树效果,不需要对原生的RecyclerView做什么改动,只要实现一个适当的Adapter就足够了。下面就看看实现上面GIF图的代码吧。

首先,写一个简单的数据模型类:

其中,itemLevel用来记录该元素的级别(从0开始是最高级,然后依次为1、2等)。itemState用来记录元素的状态,即是打开还是合并的状态。child则是展开后增加显示的元素列表,实际上就是把Adapter的数据list进行add操作的参数。

再来一个树的点击处理接口:

2个方法,分别处理展开和合并的事件,TreeItem参数很好理解,就是被点击位置的元素数据,而position也就是元素在RecyclerView列表中的数据,直接通过getAdapterPosition获取后传过来,比在List里通过元素获取位置效率要高得多。

然后就是重头戏Adapter了,布局很简单就不在文中展示了,主体是一个指示器(包括一个圆形的蓝色圆,一个显示+-号来显示展开合并状态的TextView),显示标题文字的TextView和一个底部的间隔线。

接下来就是Adapter的Java代码了,其实主要就在于onOpen和onClose2个方法的实现,以及初始化全部数据使其满足我们展开树的形式。

初始化数据,会把全部数据进行遍历,并且level从0开始,依次增加,用来合并的时候判断最终的位置。

展开的时候比较简单,把该元素的child数据添加到本身位置的后面,并且把本元素的状态改为展开状态,然后使用RecyclerView特有的局部刷新,先通知在position+1的位置插入了一定条目的数据,并且把position位置也进行单独刷新(因为该位置的元素状态变成了打开)。

onClose方法要复杂一些,因为相应位置的元素在合并的时候,不仅仅它的child级是展开的,甚至child元素的child(可以继续深入下去)也是展开的,所以需要把元素的任意级别的child元素全部从mList中删除。所以我定义了一个nextSameOrHigherLevelNodePosition变量(不要吐槽我的命名水平……),先把它的值定义为整个mList中最后一个元素的位置(如果整个mList里都没有比position元素更高级的,那么nextSameOrHigherLevelNodePosition的值就是mList.size() – 1了),然后通过循环,在mList里找到position之后的第一个级别不低于position的元素,nextSameOrHigherLevelNodePosition就是这个元素的位置了。然后通过closeChild方法继续递归一下,把所有层级的child元素的状态都改为合并的状态(要不然下次展开操作就混乱了)。接下来的操作就很容易理解了,把相应位置的元素删除,并进行通知刷新。

当然了,mList.subList(position + 1, nextSameOrHigherLevelNodePosition + 1).clear();这一行代码里有一定的玄机,感兴趣的可以自行研究。

全部代码在这里:https://github.com/QingLian/AndroidTreeRecyclerView

KaelLi

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: