Flutter树形布局插件ploeg_tree_layout的使用
Flutter树形布局插件ploeg_tree_layout的使用
该布局是Ploeg对扩展的Reingold-Tilford算法的改编,如论文所述: van der Ploeg, A. (2014), Drawing non-layered tidy trees in linear time, Softw. Pract. Exper., 44, pages 1467– 1484, doi: 10.1002/spe.2213
原始实现可以在以下GitHub仓库找到: https://github.com/cwi-swat/non-layered-tidy-trees
此代码处于公共领域,您可以自由使用。引用该论文将不胜感激!
特性
非分层整洁树布局(单棵树)
非分层整洁树布局(森林)
此实现将算法与树数据结构隔离。树通过以下方式获取:
List<V> Function() roots;
List<V> Function(V) next;
使用方法
Map<int, List<int>> simpleTree = {
0: [1, 2],
1: [3],
2: [4, 5],
3: [],
4: [],
5: []
};
Map<int, Offset> nodePositions = {};
Map<int, Size> nodeSizes = {};
var algo = PloegTreeLayout(
roots: () => [0],
next: (v) => simpleTree[v]!,
sizeGetter: (v) {
Size size =
Size(Random().nextInt(90) + 30, Random().nextInt(60) + 20);
nodeSizes[v] = size;
return size;
},
onPositionChange: (v, offset) => {nodePositions[v] = offset});
algo.layout();
完整示例
以下是一个完整的示例代码,展示了如何在Flutter中使用ploeg_tree_layout
插件来绘制树形布局。
import 'dart:math';
import 'package:example/random_tree.dart';
import 'package:flutter/material.dart';
import 'package:ploeg_tree_layout/ploeg_tree_layout.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const GraphScene(),
);
}
}
class GraphScene extends StatefulWidget {
const GraphScene({super.key});
[@override](/user/override)
State<GraphScene> createState() => _GraphSceneState();
}
class _GraphSceneState extends State<GraphScene> {
Map<int, List<int>> randomTree = RandomTree.tree(5, 2, 0);
Map<int, List<int>> randomTree2 = RandomTree.tree(5, 3, 11);
Map<int, Offset> nodePositions = {};
Map<int, Size> nodeSizes = {};
[@override](/user/override)
void initState() {
super.initState();
randomTree.addAll(randomTree2);
var algo = PloegTreeLayout(
roots: () => [0, 11],
next: (v) => randomTree[v]!,
sizeGetter: (v) {
Size size =
Size(Random().nextInt(90) + 30, Random().nextInt(60) + 20);
nodeSizes[v] = size;
return size;
},
onPositionChange: (v, offset) {
nodePositions[v] = offset;
});
algo.layout();
}
final TransformationController _transformationController = TransformationController();
[@override](/user/override)
Widget build(BuildContext context) {
return Center(
child: InteractiveViewer(
boundaryMargin: const EdgeInsets.all(20.0),
minScale: 0.0001,
maxScale: 10.5,
constrained: false,
transformationController: _transformationController,
child: SizedBox(
height: 1000,
width: 1000,
child: ColoredBox(
color: Colors.grey,
child: Stack(
children: [
CustomPaint(
size: const Size(double.infinity, double.infinity),
painter: RelationPainter(
map: randomTree,
nodePositions: nodePositions,
nodeSizes: nodeSizes),
),
..._buildNodes()
],
),
),
),
),
);
}
List<Widget> _buildNodes() {
final res = [];
nodePositions.forEach((node, offset) {
res.add(NodeWidget(
offset: offset,
size: nodeSizes[node]!,
node: node,
));
});
return res;
}
}
class NodeWidget extends StatelessWidget {
const NodeWidget(
{super.key,
required this.offset,
required this.size,
required this.node});
final Offset offset;
final Size size;
final int node;
[@override](/user/override)
Widget build(BuildContext context) {
return Positioned(
left: offset.dx,
top: offset.dy,
child: Container(
width: size.width,
height: size.height,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 1),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(10),
),
child: Center(child: Material(child: Text('$node'))),
),
);
}
}
class RelationPainter extends CustomPainter {
RelationPainter(
{required this.map,
required this.nodePositions,
required this.nodeSizes});
final Map<int, List<int>> map;
final Map<int, Offset> nodePositions;
final Map<int, Size> nodeSizes;
[@override](/user/override)
void paint(Canvas canvas, Size size) {
if (nodePositions.length > 1) {
nodePositions.forEach((index, offset) {
for (var t in map[index]!) {
canvas.drawLine(
Offset(offset.dx + nodeSizes[index]!.width / 2,
offset.dy + nodeSizes[index]!.height / 2),
Offset(nodePositions[t]!.dx + nodeSizes[t]!.width / 2,
nodePositions[t]!.dy + nodeSizes[t]!.height / 2),
Paint()
..color = Colors.black
..strokeWidth = 1);
}
});
}
}
[@override](/user/override)
bool shouldRepaint(RelationPainter oldDelegate) => true;
}
更多关于Flutter树形布局插件ploeg_tree_layout的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter树形布局插件ploeg_tree_layout的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
ploeg_tree_layout
是 Flutter 中的一个树形布局插件,用于在 Flutter 应用中实现树形结构的数据展示。它可以帮助开发者轻松地构建和展示树形结构的数据,如组织结构图、文件目录、家族树等。
安装插件
首先,你需要在 pubspec.yaml
文件中添加 ploeg_tree_layout
插件的依赖:
dependencies:
flutter:
sdk: flutter
ploeg_tree_layout: ^1.0.0 # 请确保使用最新版本
然后运行 flutter pub get
来安装插件。
基本使用
以下是一个简单的示例,展示如何使用 ploeg_tree_layout
插件来展示一个树形结构。
import 'package:flutter/material.dart';
import 'package:ploeg_tree_layout/ploeg_tree_layout.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Tree Layout Example'),
),
body: TreeLayoutExample(),
),
);
}
}
class TreeLayoutExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: TreeLayout(
root: TreeNode(
widget: Text('Root'),
children: [
TreeNode(
widget: Text('Child 1'),
children: [
TreeNode(widget: Text('Grandchild 1.1')),
TreeNode(widget: Text('Grandchild 1.2')),
],
),
TreeNode(
widget: Text('Child 2'),
children: [
TreeNode(widget: Text('Grandchild 2.1')),
TreeNode(widget: Text('Grandchild 2.2')),
],
),
],
),
),
);
}
}
解释
-
TreeLayout: 这是
ploeg_tree_layout
插件的主要组件,用于展示树形结构。它接受一个TreeNode
作为根节点。 -
TreeNode:
TreeNode
是树形结构中的一个节点。它包含一个widget
属性,用于显示该节点的内容,以及一个可选的children
属性,用于指定该节点的子节点。 -
Widget: 每个
TreeNode
的widget
属性可以是任何 Flutter widget,例如Text
、Container
等。
自定义样式
你可以通过传递不同的参数来自定义树形布局的样式。例如,你可以设置节点的间距、连接线的样式等。
TreeLayout(
root: TreeNode(
widget: Text('Root'),
children: [
TreeNode(widget: Text('Child 1')),
TreeNode(widget: Text('Child 2')),
],
),
nodeSpacing: 20.0, // 设置节点之间的间距
connectorStyle: ConnectorStyle(
color: Colors.blue, // 设置连接线的颜色
thickness: 2.0, // 设置连接线的粗细
),
)