【原创不易,转载请注明出处:https://blog.csdn.net/email_jade/article/details/86528143】

在日常开发中,我们经常会遇到展示机构树的应用场景,比如说展示某公司的组织架构,或者是展示某一个目录的结构,在flutter中,暂时还没有发现类似的开源库,那么只能自己动手撸一个了,先展示下效果图:

数据结构如下,每个机构分为两类,一类是子机构,另一类是机构成员,都有name属性,这也是日常应用中最常见的形式:

class Organ{List<Organ> subOrgans;List<Member> members;String name;Organ(this.subOrgans,this.members,this.name);
}class Member{String name;Member(this.name);
}

测试数据如下,平时我们遇到的大多数情况是顶级机构只有一个节点, 为了测试顶级下面可以挂多个机构的情况,将数据改造了下:

List<Organ> _buildData(){return [Organ([Organ([Organ([Organ([Organ(null,[Member("五级机构成员1"),Member("五级机构成员2"),Member("五级机构成员3"),Member("五级机构成员4"),],"五级机构")], [Member("四级机构成员1"),Member("四级机构成员2"),Member("四级机构成员3"),Member("四级机构成员4"),],"四级机构"),Organ([Organ(null,[Member("六级机构成员1"),Member("六级机构成员2"),Member("六级机构成员3"),Member("六级机构成员4"),],"六级机构")], [Member("七级机构成员1"),Member("七级机构成员2"),Member("七级机构成员3"),Member("七级机构成员4"),],"七级机构")], [Member("三级机构成员1"),Member("三级机构成员2"),Member("三级机构成员3"),Member("三级机构成员4"),], "三级机构")], [Member("二级机构成员1"),Member("二级机构成员2"),Member("二级机构成员3"),], "二级机构")], [Member("一级机构成员1"),Member("一级机构成员2"),Member("一级机构成员3"),Member("一级机构成员4"),Member("一级机构成员5"),], "一级机构"),Organ(null, [Member("八级机构成员1"),Member("八级机构成员2"),Member("八级机构成员3"),Member("八级机构成员4"),Member("八级机构成员5"),], "八级机构")];}
}

先说下思路,机构树列表,了解设计模式的同学应该很清楚,这个场景跟设计模式的Composite模式特别相似,参考下Composite模式的思想,保持容器与内容的一致性,因此,我们可以将机构与成员看成同一种数据,区别可能是,机构下面可能有子机构和成员,但是成员下面不可能有其他数据,是叶节点。

为了绘制的方便,我们先对原始数据进行处理,将所有的数据封装成Node节点,Node节点包含是否展开的标记expand,深度depth,类型type,唯一节点号nodeId,父节点号fatherId,还有原始数据,如下:

class Node<T>{static int typeOrgan = 10000;static int typeMember = 10001;bool expand;int depth;int type;int nodeId;int fatherId;T object;Node(this.expand,this.depth,this.type,this.nodeId,this.fatherId,this.object,);}

数据的处理如下:

   ///保存所有数据的ListList<Node> list = new List();///第一个节点的indexint nodeId = 1;///如果解析的数据是一个list列表,采用这个方法void _parseOrgans(List<Organ> organs){for(Organ organ in organs){_parseOrgan(organ);}}///递归解析原始数据,将organ递归,记录其深度,nodeID和fatherID,将根节点的fatherID置为-1,///保存原始数据为泛型Tvoid _parseOrgan(Organ organ, {int depth = 0, int fatherId = -1}) {int currentId = nodeId;list.add(Node(false, depth, Node.typeOrgan, nodeId++, fatherId, organ));List<Node<Member>> members = new List();if (organ.members != null) {for (Member member in organ.members) {members.add(Node(false, depth + 1, Node.typeMember, nodeId++, currentId, member));}}list.addAll(members);if (organ.subOrgans != null) {for (Organ organ in organ.subOrgans) {_parseOrgan(organ, depth: depth + 1, fatherId: currentId);}}}

对于树的展示,我们采用的是ListView,为了区分不同的层级,可以根据depth为每个Item增加缩进,对于树状的列表来说,最重要的是树的展开与收起,先来说树的展开,我们点击了一个机构,肯定是想展示该机构下的所有子机构和成员,那么,我们可以遍历Node列表,发现了fatherId==该机构的nodeId,那么代表这些是需要展示的数据,将其保存,插入到改机构的后面,至于为啥要插入,是因为如果直接加到展示数据的末尾,那么子成员也展示到了整个ListView的末尾,极度没有体验。然后再来说说树的收起,收起的时候,要收起所有直接挂在该机构下(子树)以及所有简介挂在该机构下(儿子的子树。。)的数据,依旧采用递归,先对已有的树进行递归,将所有与该树相关的子孙树删掉即可,本处采用的是先标记再将非标记的树替换为现有的数据的方法。

树的展示如下:

  ///扩展机构树:id代表被点击的机构id/// 做法是遍历整个list列表,将直接挂在该机构下面的节点增加到一个临时列表中,///然后将临时列表插入到被点击的机构下面void _expand(int id) {//保存到临时列表List<Node> tmp = new List();for (Node node in list) {if (node.fatherId == id) {tmp.add(node);}}//找到插入点int index = -1;int length = expand.length;for(int i=0; i<length; i++){if(id == expand[i].nodeId){index = i+1;break;}}//插入expand.insertAll(index, tmp);}///收起机构树:id代表被点击的机构id/// 做法是遍历整个expand列表,将直接和间接挂在该机构下面的节点标记,///将这些被标记节点删除即可,此处用到的是将没有被标记的节点加入到新的列表中void _collect(int id){//清楚之前的标记mark.clear();//标记_mark(id);//重新对expand赋值List<Node> tmp = new List();for(Node node in expand){if(mark.indexOf(node.nodeId) < 0){tmp.add(node);}else{node.expand = false;}}expand.clear();expand.addAll(tmp);}///标记,在收起机构树的时候用到void _mark(int id) {for (Node node in expand) {if (id == node.fatherId) {if (node.type == Node.typeOrgan) {_mark(node.nodeId);}mark.add(node.nodeId);}}}///增加根void _addRoot() {for (Node node in list) {if (node.fatherId == -1) {expand.add(node);}}}///构建元素List<Widget> _buildNode(List<Node> nodes) {List<Widget> widgets = List();if (nodes != null && nodes.length > 0) {for (Node node in nodes) {widgets.add(GestureDetector(child: ImageText(node.type == Node.typeOrgan? node.expand ? "images/expand.png" : "images/collect.png": "images/member.png",node.type == Node.typeOrgan ? (node.object as Organ).name : (node.object as Member).name,padding: node.depth * 20.0,),onTap: (){if(node.type == Node.typeOrgan){if(node.expand){ //之前是扩展状态,收起列表node.expand = false;_collect(node.nodeId);}else{ //之前是收起状态,扩展列表node.expand = true;_expand(node.nodeId);}setState(() {});}},));}}return widgets;}

做完了收起和展开,那么一个树差不多就完成了,至于树的展示顺序,因为对于不同的应用场景来说,展示的顺序也有所不同,因此,本代码暂未涉及。当然,博客里面的一些代码可能不全,完整的代码见:

GitHub地址:https://github.com/jadennn/flutter_tree

(20190214更新,增加搜索功能)

相关代码见:

https://github.com/jadennn/flutter_tree/commit/f7fa077f0aee92cb7bfb0db7cc996f7603f29c6c

     flutter很好,路还很长,让我们一起奋斗前行!

flutter入门之实现展示机构树的功能相关推荐

  1. Flutter 入门指北(Part 9)之弹窗和提示(SnackBar、BottomSheet、Dialog)

    该文已授权公众号 「码个蛋」,转载请指明出处 前面的小节把常用的一些部件都介绍了,这节介绍下 Flutter 中的一些操作提示.Flutter 中的操作提示主要有这么几种 SnackBar.Botto ...

  2. 从零开始的Flutter入门实战(二)

    目录 前言 一.Column布局 1.创建一个Column 2.添加Container 3.运行验证 二.Row布局 1.将Column改成Row 三.Column布局和Row布局的混合使用 1.Si ...

  3. Flutter入门——创建第一个Flutter项目

    Flutter入门--创建第一个Flutter项目 一.创建项目 第一个项目使用Android Studio创建,步骤如下: 先打开Android Studio,会有一个创建新的Flutter应用的选 ...

  4. Flutter 入门经典

    Flutter是Google公司推出的新一代前端框架,最初目标只是为了满足移动端跨平台的应用开发, 开发人员可使用 Flutter 在 iOS 和 Android 上快速构建高质量的原生用户界面.但如 ...

  5. Flutter入门系列(二)---Flutter的原理及美团的实践

    转载自:美团技术团队 导读 Flutter是Google开发的一套全新的跨平台.开源UI框架,支持iOS.Android系统开发,并且是未来新操作系统Fuchsia的默认开发套件.自从2017年5月发 ...

  6. Flutter入门之——HelloWorld

    Flutter入门之--HelloWorld 安装Flutter开发环境 首先要准备好Flutter的开发环境和开发所使用的IDE,这里我们使用AndroidStudio作为开发工具. Flutter ...

  7. flutter 入门示例_AnyChart入门— 10个实用示例

    flutter 入门示例 If your website is data-intensive, then you will need to make that data easy to visuali ...

  8. Flutter入门进阶之旅(二)Hello Flutter

    开题 好像几乎我们学习或者掌握任何一门编程语言都是Hello word开始的,本篇博文做为Flutter入门进阶的第一篇分享,我们也从最简单的Hello world开始,至于Flutter开发环境的配 ...

  9. 机构树的数据库设计与查询构想-使用递归算法

    需求 设计机构树,查询实验室所有机构 以某一节点为根节点,查询局部机构树 实现 思路: 自身嵌套查询 代码递归查询 数据库设计 CREATE TABLE `sys_dept` (`id` bigint ...

最新文章

  1. ORB_SLAM2代码阅读(1)——系统入口
  2. Linux下使用service启动jar包.md
  3. 支持的网卡列表_Windows 10的5G网卡折腾笔记(含采购链接)
  4. 在 Unity 中基于 Oculus DK1 的开发
  5. Kubernetes二进制部署——证书的制作和ETCD的部署
  6. JAVA软件图片浏览下载_java模拟浏览器下载图片
  7. Ue4.20 安卓开发配置及Android Studio 调试ue安卓工程
  8. 单例模式不能被继承_Spring的单例实现原理
  9. 为什么大公司一定要使用 DevOps?
  10. xshell6和xftp6的安装
  11. 电脑照片,怎么把电脑照片传到iphone手机 将电脑照片传到iphone方法【图文】
  12. word如何插入和删除脚注,尾注
  13. 微信网页授权及使用微信jssdk
  14. 创建API Signing Key
  15. -XX:CMSInitiatingOccupancyFraction
  16. 从零开始入门推荐算法工程师
  17. 用户运营:如何用B端运营思维做用户增长?
  18. ArcGIS教程:创建饼图
  19. 常见的SNS盈利模式(商业模式)
  20. WordPress安装教程(2022)|详细

热门文章

  1. android 简单的贪吃蛇小游戏
  2. 分享一个会遮掩的吊炸天登录页面
  3. 百度云分享文件自己设置密码
  4. 计算机未检测到任何网络硬件,电脑提示windows没有检测到任何网络硬件怎么办...
  5. OpenGL模拟太阳系运行
  6. R 软件的下载与安装
  7. java 获取当天0时0分0秒和 23时23分59秒 的时间
  8. 计算机网络——各层次网络互联设备
  9. 联想y7000电脑未正确启动_联想拯救者Y7000P装win7系统蓝屏|联想Y7000P重装系统蓝屏怎么解决...
  10. Lumen 安装配置