我们有一个 N 层(最多可能大约 10 层左右)嵌套数据结构,基本上类似于文件夹布局..
任何级别的每个节点都是要显示的内容的 Mime 类型或 URL ..
我的问题实际上非常简单..是否有任何可用的 Fluter Widget 可以显示这种类型的结构 - 允许在任何父级等处进行常见的“打开/关闭”??
这似乎是库存工具箱中没有的一个非常基本的 UI 元素,但我没有运气找到一个..
蒂亚!
/史蒂夫
在过去的几周里,我一直在开发一个
TreeView
小部件,并提出了一个基本结构。现在可以在pub中使用。一旦你真正知道如何去做,工作就很容易了。我必须承认,文档从来都不是我的强项,但如果有人对此有任何疑问,只需在 Github 页面上添加问题即可。
也欢迎任何改进项目的建议。
假设这是我们要使用
TreeView
小部件实现的目录结构
Desktop
|-- documents
| |-- Resume.docx
| |-- Billing-Info.docx
|-- MeetingReport.xls
|-- MeetingReport.pdf
|-- Demo.zip
在此示例中
Child
小部件,其中 documents 作为 Parent
。Child
小部件,其中 Desktop 作为 Parent
小部件。
var treeView = TreeView(
parentList: [
Parent(
parent: Text('Desktop'),
childList: ChildList(
children: [
Parent(
parent: Text('documents'),
childList: ChildList(
children: [
Text('Resume.docx'),
Text('Billing-Info.docx'),
],
),
),
Text('MeetingReport.xls'),
Text('MeetingReport.pdf'),
Text('Demo.zip'),
],
),
),
],
);
这根本不会产生任何花哨的东西。但是,您可以传递任何复杂的小部件,而不是所有
Text
小部件,它仍然可以工作。
TreeView
参加聚会有点晚了。我也一直在寻找一个用于我的 flutter 项目的树视图结构,但我找不到任何适合我的需求。所以,我创建了一个包 dynamic_treeview 。它使用父/子建立树形视图的关系。看看这是否适合您的需求。让我知道你们的想法。谢谢
这是一个树视图示例
这里的方法是用分层的
TreeNode
结构构建树。每个节点都有一个 ValueNotifier
,让树知道 isExpanded
何时发生变化。树的状态通过 expandAll()
公开 collapseAll()
和 GlobalKey
,以便您可以递归折叠/展开。
import 'package:arborio/expander.dart';
import 'package:flutter/material.dart';
///[GlobalKey] for controlling the state of the [TreeView]
class TreeViewKey<T> extends GlobalKey<_TreeViewState<T>> {
///Creates a [GlobalKey] for controlling the state of the [TreeView]
const TreeViewKey() : super.constructor();
}
///The callback function for building tree nodes, including animation values
typedef TreeViewBuilder<T> = Widget Function(
BuildContext context,
TreeNode<T> node,
bool isSelected,
Animation<double> expansionAnimation,
void Function(TreeNode<T> node) select,
);
///The callback function when the node expands or collapses
typedef ExpansionChanged<T> = void Function(TreeNode<T> node, bool expanded);
void _defaultExpansionChanged<T>(TreeNode<T> node, bool expanded) {}
void _defaultSelectionChanged<T>(TreeNode<T> node) {}
///Represents a tree node in the [TreeView]
class TreeNode<T> {
///Creates a tree node
TreeNode(
this.key,
this.data, [
List<TreeNode<T>>? children,
bool isExpanded = false,
]) : children = children ?? <TreeNode<T>>[],
isExpanded = ValueNotifier(isExpanded);
///The unique key for this node
final Key key;
///The data for this node
final T data;
///The children of this node
final List<TreeNode<T>> children;
///Whether or not this node is expanded. Changing this value will cause the
///node's expander to animate open or closed
ValueNotifier<bool> isExpanded;
}
///A tree view widget that for displaying data hierarchically
class TreeView<T> extends StatefulWidget {
///Creates a [TreeView] widget
const TreeView({
required this.nodes,
required this.builder,
required this.expanderBuilder,
ExpansionChanged<T>? onExpansionChanged,
ValueChanged<TreeNode<T>>? onSelectionChanged,
this.selectedNode,
this.indentation = const SizedBox(width: 16),
super.key,
this.animationCurve = Curves.easeInOut,
this.animationDuration = const Duration(milliseconds: 500),
}) : onExpansionChanged = onExpansionChanged ?? _defaultExpansionChanged,
onSelectionChanged = onSelectionChanged ?? _defaultSelectionChanged;
///The root nodes for this tree view, which can have children
final List<TreeNode<T>> nodes;
///Called when a node is expanded or collapsed
final ExpansionChanged<T> onExpansionChanged;
///Called when the selected node changes
final ValueChanged<TreeNode<T>> onSelectionChanged;
///The currently selected node
final TreeNode<T>? selectedNode;
///The widget to use for indentation of nodes
final Widget indentation;
///The builder for the expander icon (usually an arrow icon or similar)
final ExpanderBuilder expanderBuilder;
///The builder for the content of the expander (usually icon and text)
final TreeViewBuilder<T> builder;
///This modulates the animation for the expander when it opens and closes
final Curve animationCurve;
///The duration of the animation for the expander when it opens and closes
final Duration animationDuration;
@override
State<TreeView<T>> createState() => _TreeViewState<T>();
}
class _TreeViewState<T> extends State<TreeView<T>> {
TreeNode<T>? _selectedNode;
@override
void initState() {
super.initState();
_selectedNode = widget.selectedNode;
widget.nodes.forEach(_listen);
}
void _listen(TreeNode<T> node) {
node.children.forEach(_listen);
node.isExpanded.addListener(() => setState(() {}));
}
void collapseAll() => setState(() {
for (final node in widget.nodes) {
_setIsExpanded(node, false);
}
});
void expandAll() => setState(() {
for (final node in widget.nodes) {
_setIsExpanded(node, true);
}
});
void _setIsExpanded(TreeNode<T> node, bool isExpanded) {
for (final n in node.children) {
_setIsExpanded(n, isExpanded);
}
node.isExpanded.value = isExpanded;
}
void _handleSelection(TreeNode<T> node) {
setState(() {
_selectedNode = node;
});
widget.onSelectionChanged(node);
}
@override
Widget build(BuildContext context) => ListView(
children: widget.nodes
.map((node) => _buildNode(node, widget.onExpansionChanged))
.toList(),
);
Widget _buildNode(
TreeNode<T> node,
ExpansionChanged<T> expansionChanged,
) =>
Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: Row(
children: [
widget.indentation,
Expanded(
child: Expander<T>(
animationDuration: widget.animationDuration,
animationCurve: widget.animationCurve,
expanderBuilder: widget.expanderBuilder,
canExpand: node.children.isNotEmpty,
key: PageStorageKey<Key>(node.key),
contentBuilder: (context, isExpanded, animationValue) =>
widget.builder(
context,
node,
_selectedNode?.key == node.key,
animationValue,
_handleSelection,
),
onExpansionChanged: (expanded) {
setState(() {
node.isExpanded.value = expanded;
});
expansionChanged(node, expanded);
},
isExpanded: node.isExpanded,
children: node.children
.map((childNode) => _buildNode(childNode, expansionChanged))
.toList(),
),
),
],
),
);
@override
void dispose() {
for (final node in widget.nodes) {
for (final childNode in node.children) {
childNode.isExpanded.dispose();
}
node.isExpanded.dispose();
}
super.dispose();
}
}
Expander
,但 ExpansionTile 非常相似。如果您愿意,可以在此处使用 ExpansionTile
。
在这里领取包裹。
查看