Flutter可扩展树形菜单插件expandable_tree_menu的使用

发布于 1周前 作者 zlyuanteng 来自 Flutter

Flutter可扩展树形菜单插件expandable_tree_menu的使用

expandable_tree_menu 是一个用于构建可展开/折叠子菜单树的Flutter插件,每个节点可以递归地包含另一个树,使用“节点”的递归列表。以下是该插件的详细使用说明。

插件简介

通过 expandable_tree_menu 插件,您可以轻松创建一个树形结构的菜单,其中每个节点都可以根据需要展开或折叠其子节点。插件支持自定义节点样式、初始展开状态、图标位置和颜色等配置选项,并提供了灵活的回调接口来处理节点选择事件。

Demo

主要特性

  • 支持无限层级的树形结构
  • 自定义节点渲染器(nodeBuilder
  • 提供节点选择事件处理器(onSelect
  • 内置多种样式配置选项

配置选项

以下是 ExpandableTree 组件提供的主要配置项:

  • initiallyExpanded: 是否初始状态下全部节点都处于展开状态,默认为折叠。
  • twistyPosition: 展开/折叠图标的相对位置,默认在右侧(TwistyPosition.after)。
  • openTwistyColor: 子节点已展开时使用的图标颜色,默认基于主题色。
  • closedTwistyColor: 子节点未展开时使用的图标颜色,默认基于主题色。
  • openTwisty: 已展开时显示的自定义小部件,默认为Icons.expand_less
  • closedTwisty: 未展开时显示的自定义小部件,默认为Icons.expand_more
  • childIndent: 子节点相对于父节点缩进的距离,默认为28像素。
  • submenuDecoration: 包含子节点的子菜单装饰,默认无。
  • childrenDecoration: 子节点本身的装饰,默认无。
  • childrenMargin: 子节点周围的边距,默认无。
  • submenuMargin: 子菜单周围的边距,默认无。
  • submenuClosedColor: 子菜单关闭时的颜色,默认无。
  • submenuOpenColor: 子菜单打开时的颜色,默认无。

基本用法

下面是一个简单的例子,展示了如何使用 ExpandableTree 创建一个多级分类菜单:

ExpandableTree(
  nodes: [
    TreeNode('Category A',
      subNodes: [
        TreeNode('CatA first item'),
        TreeNode('CatA second item'),
      ],
    ),
    TreeNode('Category B',
      subNodes: [
        TreeNode('Cat B first item'),
        TreeNode('Cat B sub-category 1',
          subNodes: [
            TreeNode('Cat B1 first item'),
            TreeNode('Cat B1 second item'),
            TreeNode('Cat B1 third item'),
            TreeNode('Cat B1 final item'),
          ],
        ),
      ],
    ),
  ],
  nodeBuilder: (context, nodeValue) => Card(
    child: Text(nodeValue.toString()),
  ),
  onSelect: (node) => _nodeSelected(context, node),
)

完整示例代码

为了更好地理解插件的工作原理,这里提供了一个完整的示例应用,包括数据加载、页面跳转等功能。

import 'package:expandable_tree_menu/expandable_tree_menu.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Getting Started',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final nodes = <TreeNode>[];

  void _addData(data) {
    setState(() {
      nodes.addAll(data);
    });
  }

  Future<List<TreeNode>> fetchData() async {
    // 模拟从服务器或其他地方获取数据
    await Future.delayed(Duration(seconds: 1));
    return _dataLoad();
  }

  @override
  void initState() {
    super.initState();
    fetchData().then(_addData);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Menu demo'),
      ),
      body: Column(
        children: [
          Flexible(
            child: SingleChildScrollView(
              child: ExpandableTree(
                nodes: nodes,
                nodeBuilder: _nodeBuilder,
                onSelect: (node) => _nodeSelected(context, node),
              ),
            ),
          ),
        ],
      ),
    );
  }

  /// 处理节点点击事件
  void _nodeSelected(BuildContext context, nodeValue) {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (context) => DetailPage(value: nodeValue),
    ));
  }

  /// 构建节点UI
  Widget _nodeBuilder(BuildContext context, nodeValue) {
    return Card(
      margin: EdgeInsets.symmetric(vertical: 1),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text(nodeValue.toString()),
      ),
    );
  }
}

// 详情页组件
class DetailPage extends StatelessWidget {
  final value;

  const DetailPage({Key? key, this.value}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(value.toString()),
      ),
      body: Center(
        child: Text(value.toString()),
      ),
    );
  }
}

// 模拟的数据加载函数
Future<List<TreeNode>> _dataLoad() async {
  // 实际开发中应替换为真正的数据源
  return [
    TreeNode<String>('Some String'),
    TreeNode<String>('Node with sub-items', subNodes: [
      TreeNode<String>('First sub node'),
      TreeNode<String>('Second sub node'),
    ])
  ];
}

这个例子演示了如何结合 ExpandableTree 和路由导航来实现一个交互式的树形菜单。希望这能帮助您快速上手并充分利用 expandable_tree_menu 插件!如果有任何问题或建议,请随时留言交流。


更多关于Flutter可扩展树形菜单插件expandable_tree_menu的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter可扩展树形菜单插件expandable_tree_menu的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用expandable_tree_menu插件来创建一个可扩展的树形菜单的示例代码。

首先,确保你已经在pubspec.yaml文件中添加了expandable_tree_menu依赖:

dependencies:
  flutter:
    sdk: flutter
  expandable_tree_menu: ^最新版本号  # 请替换为当前最新版本号

然后运行flutter pub get来获取依赖。

接下来,在你的Dart文件中,你可以按照以下步骤来创建树形菜单:

import 'package:flutter/material.dart';
import 'package:expandable_tree_menu/expandable_tree_menu.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Expandable Tree Menu Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Expandable Tree Menu Demo'),
        ),
        body: TreeMenuWidget(),
      ),
    );
  }
}

class TreeMenuWidget extends StatefulWidget {
  @override
  _TreeMenuWidgetState createState() => _TreeMenuWidgetState();
}

class _TreeMenuWidgetState extends State<TreeMenuWidget> {
  // 定义树形菜单的数据结构
  final List<TreeNode> treeData = [
    TreeNode(
      title: 'Node 1',
      children: [
        TreeNode(
          title: 'Node 1.1',
          children: [
            TreeNode(title: 'Node 1.1.1'),
            TreeNode(title: 'Node 1.1.2'),
          ],
        ),
        TreeNode(title: 'Node 1.2'),
      ],
    ),
    TreeNode(
      title: 'Node 2',
      children: [
        TreeNode(title: 'Node 2.1'),
        TreeNode(title: 'Node 2.2'),
      ],
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: ExpandableTreeMenu(
        treeData: treeData,
        onNodeTap: (TreeNode node) {
          // 节点点击事件处理
          print('Node tapped: ${node.title}');
        },
        leading: const Icon(Icons.arrow_drop_down),
        trailing: const Icon(Icons.chevron_right),
      ),
    );
  }
}

// TreeNode 类定义(假设expandable_tree_menu插件没有自带此类)
class TreeNode {
  final String title;
  final List<TreeNode>? children;

  TreeNode({required this.title, this.children});
}

在这个示例中,我们定义了一个简单的树形菜单数据结构TreeNode,它包含一个标题和一个可选的子节点列表。然后,我们在TreeMenuWidget中使用ExpandableTreeMenu组件来渲染这个树形菜单。

注意:

  • ExpandableTreeMenu组件接受一个treeData参数,它是一个TreeNode对象的列表。
  • onNodeTap回调函数在节点被点击时触发,这里我们只是简单地打印了被点击的节点的标题。
  • leadingtrailing参数分别定义了节点展开/收起图标和右侧图标,这里我们使用了Flutter的内置图标。

请确保expandable_tree_menu插件的实际用法和API与示例代码一致,因为插件的API可能会随着版本更新而发生变化。如果遇到问题,请查阅插件的官方文档或GitHub仓库。

回到顶部