flutter入门之实现展示机构树的功能

    【原创不易,转载请注明出处: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,
      );

}

    数据的处理如下:

   ///保存所有数据的List
  List<Node> list = new List();
  ///第一个节点的index
  int nodeId = 1;

  ///如果解析的数据是一个list列表,采用这个方法
  void _parseOrgans(List<Organ> organs){
    for(Organ organ in organs){
      _parseOrgan(organ);
    }
  }

  ///递归解析原始数据,将organ递归,记录其深度,nodeID和fatherID,将根节点的fatherID置为-1,
  ///保存原始数据为泛型T
  void _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很好,路还很长,让我们一起奋斗前行!

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值