【原創不易,轉載請註明出處: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很好,路還很長,讓我們一起奮鬥前行!