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很好,路還很長,讓我們一起奮鬥前行!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章