Flutter中如何将JSON数据转换为父子层级结构的树形视图?
从JSON动态生成Flutter树形视图方案
需求说明
我正在开发一款Flutter应用,需要从JSON数据生成树形视图,预期效果为多层级可展开/折叠的树形列表:根节点包含Root 1和Root 2,展开Root 1可看到Node 1.1和Node 1.2,展开Node 1.1又可看到Node 1.1.1和Node 1.1.2;展开Root 2可看到Node 2.1和Node 2.2。
数据结构
表格结构
| ParentID | ChildID | Title |
|---|---|---|
| 1 | 0 | Root 1 |
| 2 | 0 | Root 2 |
| 3 | 1 | Node 1.1 |
| 4 | 3 | Node 1.1.1 |
| 5 | 3 | Node 1.1.2 |
| 6 | 1 | Node 1.2 |
| 7 | 2 | Node 2.1 |
| 8 | 2 | Node 2.2 |
对应JSON数据
[ { "ParentID": 1, "ChildID": 0, "Title": "Root 1" }, { "ParentID": 2, "ChildID": 0, "Title": "Root 2" }, { "ParentID": 3, "ChildID": 1, "Title": "Node 1.1" }, { "ParentID": 4, "ChildID": 3, "Title": "Node 1.1.1" }, { "ParentID": 5, "ChildID": 3, "Title": "Node 1.1.2" }, { "ParentID": 6, "ChildID": 1, "Title": "Node 1.2" }, { "ParentID": 7, "ChildID": 2, "Title": "Node 2.1" }, { "ParentID": 8, "ChildID": 2, "Title": "Node 2.2" } ]
当前我通过硬编码MyNode列表生成树形视图:
static const List<MyNode> roots = <MyNode>[ MyNode( title: 'Root 1', children: <MyNode>[ MyNode( title: 'Node 1.1', children: <MyNode>[ MyNode(title: 'Node 1.1.1'), MyNode(title: 'Node 1.1.2'), ], ), MyNode(title: 'Node 1.2'), ], ), MyNode( title: 'Root 2', children: <MyNode>[ MyNode(title: 'Node 2.1'), MyNode(title: 'Node 2.2') ], ), ];
以下是从JSON动态生成树形结构的完整方案:
1. 定义数据模型
首先定义对应JSON的实体类,以及树形结构节点类:
import 'dart:convert'; // 对应JSON的原始数据模型 class NodeData { final int parentID; final int childID; final String title; NodeData({ required this.parentID, required this.childID, required this.title, }); // 从JSON解析为NodeData对象 factory NodeData.fromJson(Map<String, dynamic> json) { return NodeData( parentID: json['ParentID'], childID: json['ChildID'], title: json['Title'], ); } } // 树形结构节点类(调整为可变子节点列表) class MyNode { final String title; final List<MyNode> children; MyNode({ required this.title, List<MyNode>? children, }) : children = children ?? []; // 默认空列表,支持动态添加子节点 }
2. 实现平级数据转树形结构逻辑
通过Map快速查找父节点,建立层级关系:
List<MyNode> convertToTree(List<NodeData> nodeDataList) { final Map<int, MyNode> nodeMap = {}; final List<MyNode> rootNodes = []; // 第一步:创建所有节点并存入Map(key为节点的ParentID,作为唯一标识) for (final data in nodeDataList) { nodeMap[data.parentID] = MyNode(title: data.title); } // 第二步:遍历建立父子关系 for (final data in nodeDataList) { final currentNode = nodeMap[data.parentID]!; if (data.childID == 0) { // ChildID为0的是根节点,加入根列表 rootNodes.add(currentNode); } else { // 找到父节点(父节点的ParentID等于当前节点的ChildID) final parentNode = nodeMap[data.childID]; if (parentNode != null) { parentNode.children.add(currentNode); } } } return rootNodes; } // 解析JSON字符串为NodeData列表 List<NodeData> parseJsonString(String jsonStr) { final List<dynamic> jsonList = jsonDecode(jsonStr); return jsonList.map((json) => NodeData.fromJson(json)).toList(); }
3. 实现树形视图Widget
通过递归渲染ExpansionTile实现可展开/折叠的树形列表:
import 'package:flutter/material.dart'; class TreeView extends StatelessWidget { final List<MyNode> nodes; const TreeView({super.key, required this.nodes}); @override Widget build(BuildContext context) { return ListView.builder( shrinkWrap: true, itemCount: nodes.length, itemBuilder: (context, index) => _buildTreeNode(nodes[index]), ); } Widget _buildTreeNode(MyNode node) { if (node.children.isEmpty) { // 叶子节点直接显示ListTile return ListTile(title: Text(node.title)); } else { // 有子节点的节点用ExpansionTile实现展开/折叠 return ExpansionTile( title: Text(node.title), children: node.children.map(_buildTreeNode).toList(), ); } } }
4. 实际使用示例
在页面中加载数据并渲染树形视图:
class TreePage extends StatelessWidget { // 模拟从数据库获取的JSON字符串 final String jsonData = ''' [ {"ParentID":1,"ChildID":0,"Title":"Root 1"}, {"ParentID":2,"ChildID":0,"Title":"Root 2"}, {"ParentID":3,"ChildID":1,"Title":"Node 1.1"}, {"ParentID":4,"ChildID":3,"Title":"Node 1.1.1"}, {"ParentID":5,"ChildID":3,"Title":"Node 1.1.2"}, {"ParentID":6,"ChildID":1,"Title":"Node 1.2"}, {"ParentID":7,"ChildID":2,"Title":"Node 2.1"}, {"ParentID":8,"ChildID":2,"Title":"Node 2.2"} ] '''; TreePage({super.key}); @override Widget build(BuildContext context) { // 解析并转换为树形结构 final nodeDataList = parseJsonString(jsonData); final treeRoots = convertToTree(nodeDataList); return Scaffold( appBar: AppBar(title: const Text('树形视图')), body: Padding( padding: const EdgeInsets.all(8.0), child: TreeView(nodes: treeRoots), ), ); } }
注意事项
- 确保JSON数据中每个节点的
ChildID都能匹配到对应的父节点(即存在ParentID等于该ChildID的节点),避免出现无法归属的孤儿节点。 - 如果数据量较大,可考虑优化递归渲染性能,比如使用懒加载或缓存节点状态。
内容的提问来源于stack exchange,提问作者Sulfy
相关产品推荐
相关产品推荐

