Flutter侧边菜单插件flutter_smenus的使用

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

Flutter侧边菜单插件flutter_smenus的使用

概览

Flutter SMenus

logo
GitHub Pub Version GitHub repo size

自定义菜单设计到您的想象!

功能

查看展示区以了解所有类型的菜单及其示例。

菜单

这些是此包中包含的主要菜单。每个都支持自定义菜单项。

名称 描述
SResizableMenu 可以程序化或物理方式调整大小的菜单
SSlideMenu 滑动菜单,可以滑入、滑出或主体滑离来揭示菜单
SDropdownMenuCascade 经典下拉菜单
SDropdownMenuMorph 使用Hero的下拉弹出菜单,目前还在开发中,大部分功能正常但可能有一些问题

开始使用

安装

访问安装标签以获取更多信息。

pubspec.yaml文件中添加以下依赖:

dependencies:
    flutter_smenus: ^2.0.0

或者在项目终端中运行:

$ flutter pub add flutter_smenus

记得运行 flutter pub get

导入

import 'package:flutter_smenus/flutter_smenus.dart';

使用

完成上述步骤后,即可开始使用该插件!

以下是如何实现自定义菜单的示例。

展示

简单应用使用所有菜单

All Menu Showcase

代码

SResizableMenu

SResizableMenu(
    controller: SMenuController(),
    position: SMenuPosition.left,
    items: [],
    body: Container(),
)

参数

SResizableMenu(
    controller: SMenuController(), // 控制器
    position: SMenuPosition.left, // 菜单位置
    items: [], // 菜单项列表
    body: Container(), // 主体内容
)

SSlideMenu

SSlideMenu(
    controller: SMenuController(),
    position: SMenuPosition.left,
    items: [],
    isBodyMovable = false,
    body: Container(),
)

参数

SSlideMenu(
    controller: SMenuController(), // 控制器
    position: SMenuPosition.left, // 菜单位置
    items: [], // 菜单项列表
    isBodyMovable: false, // 主体是否可移动
    body: Container(), // 主体内容
)

SDropdownMenuCascade

Cascade Dropdown Menu Showcase
SDropdownMenuCascade(
    controller: SMenuController(),
    items: [],
    child: Container(),
)

参数

SDropdownMenuCascade(
    controller: SMenuController(), // 控制器
    items: [], // 菜单项列表
    child: Container(), // 初始显示的组件
)

SDropdownMenuMorph

SDropdownMenuMorph(
    controller: SMenuController(),
    items: [],
    child: Container(),
)

参数

SDropdownMenuMorph(
    controller: SMenuController(), // 控制器
    items: [], // 菜单项列表
    child: Container(), // 初始显示的组件
)

SMenuItem

SMenuItem(
    value: 'one',
    builder: (context, style, child, onPressed) {
        return Text('Menu Item');
    },
)

参数

SMenuItem(
    value: 'one', // 值
    builder: (context, style, child, onPressed) { // 构建器函数
        return Text('Menu Item');
    },
)

示例代码

import 'package:flutter/material.dart';

import 'package:flutter_smenus/dropdown.dart';
import 'package:flutter_smenus/menu.dart';
import 'package:flutter_smenus/menu_item.dart';
import 'package:flutter_smenus/base.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 SMenus',
      theme: ThemeData(),
      home: const MyHomePage(title: 'Flutter SMenus'),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  SMenuController detailsMenuController = SMenuController();
  SMenuController leftMenuController = SMenuController();
  SMenuController consoleMenuController = SMenuController();

  Map<String, dynamic> chosenFile = data[1];
  int selectedIndex = 0;
  List<GlobalKey> keys = List.generate(4, (index) => GlobalKey());
  List<double> heights = [];

  [@override](/user/override)
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      setState(() {
        heights = keys
            .map((key) => key.currentContext!.size!.height)
            .toList()
            .reversed
            .toList();
      });
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    const midDropdownButtonStyle = SMenuItemStyle(
        borderRadius: BorderRadius.zero, alignment: MainAxisAlignment.center);

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: const [
          Padding(
            padding: EdgeInsets.only(right: 12.0),
            child: Center(
              child: SizedBox.square(
                dimension: 32,
                child: SDropdownMenuCascade(
                  position: SDropdownMenuPosition.bottomLeft,
                  icon: Icon(
                    Icons.settings,
                    size: 22,
                  ),
                  items: [
                    SMenuItem.clickable(
                      value: 1,
                      title: Text('First Option (1)'),
                      style: SMenuItemStyle(
                        borderRadius: BorderRadius.only(
                          topLeft: Radius.circular(15),
                          topRight: Radius.circular(15),
                        ),
                      ),
                    ),
                    SMenuItem.clickable(
                      value: 2,
                      title: Text('Option 2'),
                      style: midDropdownButtonStyle,
                    ),
                    SMenuItem.clickable(
                      value: 3,
                      title: Text('Option 3'),
                      style: midDropdownButtonStyle,
                    ),
                    SMenuItem.clickable(
                      value: 4,
                      title: Text('Option 4'),
                      style: midDropdownButtonStyle,
                    ),
                    SMenuItem.clickable(
                      value: 5,
                      title: Text('Last Option (5)'),
                      style: SMenuItemStyle(
                        borderRadius: BorderRadius.only(
                          bottomLeft: Radius.circular(15),
                          bottomRight: Radius.circular(15),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          )
        ],
      ),
      body: SResizableMenu(
        closedSize: 60,
        controller: leftMenuController,
        resizable: false,
        position: SMenuPosition.left,
        style: SMenuStyle(
            border: Border.all(color: Colors.black12, width: 1),
            borderRadius: const BorderRadius.horizontal(right: Radius.circular(15))),
        header: TextButton(
            onPressed: () {
              leftMenuController.toggle();
              setState(() {});
            },
            child: Icon((leftMenuController.state.value == SMenuState.closed ||
                    leftMenuController.state.value == SMenuState.closing)
                ? Icons.menu
                : Icons.menu_open_outlined)),
        footer: const Padding(
          padding: EdgeInsets.symmetric(horizontal: 25),
          child: Text(
            'This is a basic footer',
            textAlign: TextAlign.center,
            maxLines: 1,
          ),
        ),
        builder: (context, items) {
          double offset = 0.0;
          int val = -1;
          for (int i = 0; i < heights.length; i++) {
            if (items[i] is SMenuItemButton) {
              val++;
            }

            if (val == selectedIndex) {
              offset += heights[i] / 2;
              break;
            }
            if (i == 0) {
              offset += 20 / 2;
            }
            offset += heights[i];
          }

          return Stack(
            children: [
              SingleChildScrollView(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: items,
                ),
              ),
              AnimatedContainer(
                  duration: const Duration(milliseconds: 250),
                  curve: Curves.easeInOutCirc,
                  margin: EdgeInsets.only(top: offset, left: 2),
                  height: 25,
                  width: 5.0,
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(5),
                      color: Theme.of(context).colorScheme.onPrimary)),
            ],
          );
        },
        items: [
          SMenuItemButton(
            key: keys[0],
            text: 'Home',
            isSelected: selectedIndex == 0,
            icon: Icons.home,
            onTap: () {
              setState(() {
                selectedIndex = 0;
              });
            },
          ),
          SMenuItemButton(
            key: keys[1],
            text: 'A Page',
            isSelected: selectedIndex == 1,
            icon: Icons.file_open,
            onTap: () {
              setState(() {
                selectedIndex = 1;
              });
            },
          ),
          SMenuItemButton(
            key: keys[2],
            text: 'Another Page',
            isSelected: selectedIndex == 2,
            icon: Icons.document_scanner,
            onTap: () {
              setState(() {
                selectedIndex = 2;
              });
            },
          ),
          SMenuItem.label(
            key: keys[3],
            title: const Padding(
              padding: EdgeInsets.symmetric(horizontal: 15),
              child: Text(
                'This is a label',
                textAlign: TextAlign.center,
                maxLines: 1,
              ),
            ),
            style: midDropdownButtonStyle,
          ),
        ],
        body: SResizableMenu(
          controller: consoleMenuController,
          position: SMenuPosition.bottom,
          items: [
            SMenuItem(
              builder: (context, style, child) {
                return Container(
                  height: MediaQuery.of(context).size.height / 3.5,
                  width: MediaQuery.of(context).size.width,
                  decoration: BoxDecoration(
                      color: Colors.black12,
                      borderRadius: BorderRadius.circular(15)),
                );
              },
            )
          ],
          header: SizedBox(
            height: 50,
            child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Flexible(
                      child: Text(
                    'Command Bar',
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                  )),
                  Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const SizedBox.square(
                        dimension: 40,
                        child: Center(
                          child: SDropdownMenuMorph(
                            height: 150,
                            position: SDropdownMenuPosition.topLeft,
                            icon: Icon(Icons.menu),
                            items: [
                              SMenuItem.clickable(
                                value: 1,
                                title: Text('First Option (1)'),
                                style: SMenuItemStyle(
                                  borderRadius: BorderRadius.only(
                                    topLeft: Radius.circular(15),
                                    topRight: Radius.circular(15),
                                  ),
                                ),
                              ),
                              SMenuItem.clickable(
                                value: 2,
                                title: Text('Option 2'),
                                style: midDropdownButtonStyle,
                              ),
                              SMenuItem.clickable(
                                value: 3,
                                title: Text('Option 3'),
                                style: midDropdownButtonStyle,
                              ),
                              SMenuItem.clickable(
                                value: 4,
                                title: Text('Option 4'),
                                style: midDropdownButtonStyle,
                              ),
                              SMenuItem.clickable(
                                value: 5,
                                title: Text('Last Option (5)'),
                                style: SMenuItemStyle(
                                  borderRadius: BorderRadius.only(
                                    bottomLeft: Radius.circular(15),
                                    bottomRight: Radius.circular(15),
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      TextButton(
                          onPressed: () {
                            consoleMenuController.toggle();
                          },
                          child: const Icon(Icons.keyboard_command_key)),
                    ],
                  ),
                ]),
          ),
          body: SSlideMenu(
            closedSize: 60,
            openSize: 400,
            position: SMenuPosition.right,
            controller: detailsMenuController,
            isBodyMovable: false,
            items: [
              for (String key in chosenFile.keys)
                SMenuItem(
                  child: Column(
                    children: [
                      Row(
                        children: [
                          Padding(
                            padding: const EdgeInsets.only(left: 8.0),
                            child: Text(
                              '$key:',
                              style: const TextStyle(fontSize: 16),
                            ),
                          ),
                        ],
                      ),
                      Text(chosenFile[key]),
                    ],
                  ),
                ),
            ],
            body: Container(
              color: Colors.white,
              child: Center(
                  child: ListView(
                children: [
                  for (Map<String, dynamic> item in data)
                    ListTile(
                      leading: Text(item['name']),
                      title: Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Text(item['location']),
                        ],
                      ),
                      trailing: Text(item['size']),
                      onTap: () {
                        setState(() {
                          chosenFile = item;
                        });
                        detailsMenuController.toggle();
                      },
                    )
                ],
              )),
            ),
          ),
        ),
      ),
    );
  }
}

class SMenuItemButton<T> extends SMenuItem {
  const SMenuItemButton({
    super.key,
    this.onTap,
    super.style = const SMenuItemStyle(),
    required this.icon,
    this.selectedTextColor,
    this.selectedIconColor,
    this.textColor,
    this.iconColor,
    this.text,
    this.isSelected = false,
    this.onHover,
    this.onLongPress,
  });

  final IconData icon;
  final Color? selectedTextColor;
  final Color? selectedIconColor;
  final Color? textColor;
  final Color? iconColor;
  final String? text;
  final bool isSelected;
  final void Function()? onTap;
  final void Function(bool)? onHover;
  final void Function()? onLongPress;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return SMenuItem(
      style: style,
      child: AnimatedContainer(
        height: 45,
        margin: const EdgeInsets.only(top: 5),
        decoration: BoxDecoration(
          borderRadius: style.borderRadius,
          color: isSelected
              ? Theme.of(context).colorScheme.primary
              : style.bgColor ?? Theme.of(context).colorScheme.onPrimary,
        ),
        duration: const Duration(milliseconds: 250),
        child: TextButton(
          style: ButtonStyle(
              shape: MaterialStatePropertyAll(
                  RoundedRectangleBorder(borderRadius: style.borderRadius))),
          onPressed: onTap,
          onHover: onHover,
          onLongPress: onLongPress,
          child: Row(
            children: [
              Padding(
                padding: const EdgeInsets.all(0.0),
                child: Icon(
                  icon,
                  color: isSelected
                      ? selectedIconColor ??
                          Theme.of(context).colorScheme.onPrimary
                      : style.accentColor ??
                          iconColor ??
                          Theme.of(context).colorScheme.primary,
                ),
              ),
              Flexible(
                child: Padding(
                  padding: const EdgeInsets.only(left: 5),
                  child: Text(
                    text ?? '',
                    style: TextStyle(
                        color: isSelected
                            ? selectedTextColor ??
                                Theme.of(context).colorScheme.onPrimary
                            : style.accentColor ??
                                textColor ??
                                Theme.of(context).colorScheme.primary),
                    overflow: TextOverflow.fade,
                    maxLines: 1,
                    softWrap: false,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

const List<Map<String, dynamic>> data = [
  {
    'name': 'File 1',
    'created': '1/1/2000',
    'modified': '2/2/2023',
    'author': 'User 1',
    'type': 'svg',
    'location': '/usr/bin/mktemp',
    'size': '12.09 TB',
    'security': 'drwx------x',
  },
  {
    'name': 'File 2',
    'created': '2/2/2000',
    'modified': '3/3/2023',
    'author': 'User 2',
    'type': 'txt',
    'location': 'C:\\Windows\\Containers\\serviced',
    'size': '2 MB',
    'security': 'lrwxr-xr-x',
  },
  {
    'name': 'File 3',
    'created': '3/3/2000',
    'modified': '4/4/2023',
    'author': 'User 3',
    'type': 'dart',
    'location': '/User/User 3/bin/app.dart',
    'size': '6 GB',
    'security': '-rw-r--r--',
  },
  {
    'name': 'File 4',
    'created': '4/4/2000',
    'modified': '5/5/2023',
    'author': 'User 4',
    'type': 'pdf',
    'location': '/dev',
    'size': '15 KB',
    'security': '-rw-------',
  }
];

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

1 回复

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


当然,下面是一个关于如何在Flutter中使用flutter_smenus插件来实现侧边菜单的示例代码。flutter_smenus是一个用于创建侧边导航菜单的Flutter插件。请注意,这个插件的具体用法和API可能会根据版本有所变化,因此请参考最新的官方文档以获取最准确的信息。

首先,你需要在pubspec.yaml文件中添加flutter_smenus依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_smenus: ^最新版本号  # 请替换为实际的最新版本号

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

接下来,是一个完整的示例代码,展示如何使用flutter_smenus来创建一个带有侧边菜单的应用:

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

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

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

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

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  late ScaffoldController _scaffoldController;

  @override
  void initState() {
    super.initState();
    _scaffoldController = ScaffoldController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      controller: _scaffoldController,
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: <Widget>[
            DrawerHeader(
              child: Text('Drawer Header'),
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
            ),
            ListTile(
              leading: Icon(Icons.home),
              title: Text('Home'),
              onTap: () {
                Navigator.pop(context); // 关闭抽屉菜单
                // 这里可以添加跳转到Home页面的代码
              },
            ),
            ListTile(
              leading: Icon(Icons.settings),
              title: Text('Settings'),
              onTap: () {
                Navigator.pop(context); // 关闭抽屉菜单
                // 这里可以添加跳转到Settings页面的代码
              },
            ),
          ],
        ),
      ),
      appBar: AppBar(
        title: Text('Main Page'),
        leading: Builder(
          builder: (BuildContext context) {
            return IconButton(
              icon: Icon(Icons.menu),
              onPressed: () {
                _scaffoldController.openDrawer();
              },
              tooltip: MaterialLocalizations.of(context).openDrawerTooltip,
            );
          },
        ),
      ),
      body: Center(
        child: Text('Hello, Flutter!'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 这里可以添加按钮点击事件处理代码
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

// 注意:上面的代码实际上并没有直接使用flutter_smenus插件,因为flutter_smenus的具体API和用法
// 在我最后的知识更新中并没有详细记录。但通常,一个侧边菜单插件会提供一个更简便的方式来定义菜单项和它们的行为。
// 如果flutter_smenus有提供类似DrawerBuilder或者类似的封装组件,你可以按照其文档替换上面的Drawer实现。
// 例如,如果flutter_smenus有一个类似下面的API(这只是一个假设的示例):

// SMenus(
//   items: [
//     SMenuItem(icon: Icons.home, title: 'Home', onTap: () { /* 跳转到Home页面 */ }),
//     SMenuItem(icon: Icons.settings, title: 'Settings', onTap: () { /* 跳转到Settings页面 */ }),
//   ],
//   onOpen: () { /* 菜单打开时的回调 */ },
//   onClose: () { /* 菜单关闭时的回调 */ },
// )

// 请参考flutter_smenus的官方文档和示例代码来获取准确的用法。

注意:由于flutter_smenus的具体API和用法可能有所不同,上述代码中的SMenusSMenuItem等组件是基于假设的。在实际使用中,你应该参考flutter_smenus的官方文档和示例代码来了解如何正确使用该插件。如果flutter_smenus提供了与Drawer相似的封装组件,你可以按照其文档进行替换和修改。

回到顶部