Flutter Widget 显示嵌套树结构?

问题描述 投票:0回答:5

我们有一个 N 层(最多可能大约 10 层左右)嵌套数据结构,基本上类似于文件夹布局..

任何级别的每个节点都是要显示的内容的 Mime 类型或 URL ..

我的问题实际上非常简单..是否有任何可用的 Fluter Widget 可以显示这种类型的结构 - 允许在任何父级等处进行常见的“打开/关闭”??

这似乎是库存工具箱中没有的一个非常基本的 UI 元素,但我没有运气找到一个..

蒂亚!

/史蒂夫

user-interface tree treeview flutter
5个回答
6
投票

在过去的几周里,我一直在开发一个

TreeView
小部件,并提出了一个基本结构。现在可以在pub中使用。一旦你真正知道如何去做,工作就很容易了。我必须承认,文档从来都不是我的强项,但如果有人对此有任何疑问,只需在 Github 页面上添加问题即可。

也欢迎任何改进项目的建议。

示例代码

假设这是我们要使用

TreeView
小部件实现的目录结构

Desktop
|-- documents
|   |-- Resume.docx
|   |-- Billing-Info.docx
|-- MeetingReport.xls
|-- MeetingReport.pdf
|-- Demo.zip

在此示例中

  1. Resume.docxBilling-Info.docx
    Child
    小部件,其中 documents 作为
    Parent
  2. documentsMeetingReport.xlsMeetingReport.xlsDemo.zip
    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

的应用程序的屏幕截图


3
投票

参加聚会有点晚了。我也一直在寻找一个用于我的 flutter 项目的树视图结构,但我找不到任何适合我的需求。所以,我创建了一个包 dynamic_treeview 。它使用父/子建立树形视图的关系。看看这是否适合您的需求。让我知道你们的想法。谢谢


1
投票

检查flutter_simple_treeview。这是演示


0
投票

有一个自定义组件可以满足您的需要:Screenshot Preview

https://github.com/AndrewTran2018/flutter-piggy-treeview


0
投票

这是一个树视图示例

这里的方法是用分层的

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

在这里领取包裹。

查看

实时示例

© www.soinside.com 2019 - 2024. All rights reserved.