Flutter侧边栏菜单插件flutter_drawer_menu的使用

Flutter侧边栏菜单插件flutter_drawer_menu的使用

侧边栏菜单

侧边栏菜单是一种通常位于页面左侧的菜单,可用于导航或其他功能。菜单通过从主内容区域任何部分滑动来显示。主内容会以视差效果移动。它支持嵌套小部件的水平滚动,并拦截OverscrollNotification以相应地移动菜单。如果小部件宽度大于600dp,它还可以在平板模式下工作。

图片展示

Android iOS 自定义菜单背景
Android iOS Custom
平板模式
Tablet

使用说明

1. 添加依赖到你的项目pubspec.yaml文件

dependencies:
  flutter_drawer_menu: ^0.1.2

运行 flutter packages get 在项目的根目录下。

2. 导入flutter_drawer_menu库

import 'package:flutter_drawer_menu/drawer_menu.dart';

现在你可以在代码中使用 DrawerMenu 作为小部件。

3. 使用DrawerMenu

如果你想要管理 DrawerMenu 的状态或订阅事件,你需要创建一个 DrawerMenuController

final _controller = DrawerMenuController();

创建菜单:

DrawerMenu(
  controller: _controller,
  menu: _buildMenu(),
  body: _buildBody(),
);

如果你想为Android配置透明导航栏(与示例相同),你需要调用以下方法(例如,在 initState 中):

SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);

并设置 SystemUiOverlayStyle 字段:

systemNavigationBarColor: Colors.transparent,
systemNavigationBarContrastEnforced: false

如何管理DrawerMenu:

_controller.open(animated: true);
_controller.close(animated: true);
_controller.toggle(animated: true);

你可以通过控制器订阅 isOpen, scrollPosition, isTablet 状态变化事件。

ValueListenableBuilder<bool>(
  valueListenable: _controller.isOpenNotifier,
  builder: (context, value, _) {
    return Text(value ? "open": "closed");
  }
)

DrawerMenu属性

属性名 类型 描述
animationDuration Duration 切换动画持续时间(默认300ms)
tabletModeMinScreenWidth double 激活平板模式的最小宽度(显示侧边菜单)
tabletModeSideMenuWidth double 平板模式下的侧边菜单宽度
rightMargin double 菜单的右边缘距离。默认为70。
menuOverlapWidth double 菜单覆盖在主体上的宽度。此设置用于菜单装饰(移动阴影和蒙版层)。默认为0。
controller DrawerMenuController? 控制DrawerMenu行为的工具。它还允许订阅DrawerMenu状态变化的事件。
scrimColor Color? 设置在抽屉打开时遮蔽主要内容的颜色。默认为Color(0x44ffffff)。
scrimBlurEffect bool 打开菜单时应用模糊效果。默认为False。
shadowColor Color? 右侧菜单阴影的颜色。默认为Color(0x22000000)。
shadowWidth double 右侧菜单阴影的宽度。默认为35。
bodyParallaxFactor double 当菜单打开时应用于主体的视差效果乘数。0 - 主体与菜单一起移动。1 - 主体保持不动。默认为0.5。
useRepaintBoundaries bool 使用RepaintBoundary来隔离菜单和主体小部件的渲染以提高重绘性能。默认为True。
backgroundColor Color 菜单和主体下方的背景颜色。默认为Colors.white。
dragMode DragMode 拖动模式设置(never, always, onlyFling)。onlyFling - 菜单仅通过手势打开。

完整示例

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_drawer_menu/flutter_drawer_menu.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '侧边栏菜单Demo',
      theme: ThemeData.light(useMaterial3: false).copyWith(
        appBarTheme: const AppBarTheme(
            backgroundColor: Colors.black,
            foregroundColor: Colors.white,
            systemOverlayStyle: SystemUiOverlayStyle(
              // Android部分。
              statusBarColor: Colors.transparent,
              statusBarIconBrightness: Brightness.light,
              systemNavigationBarColor: Colors.transparent,
              systemNavigationBarContrastEnforced: false,
              systemNavigationBarIconBrightness: Brightness.light,
              // iOS部分。
              // 当Android设置为暗色时,iOS设置为亮色。奇怪。
              statusBarBrightness: Brightness.dark,
            )),
      ),
      home: const MyHomePage(title: '侧边栏菜单Demo'),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  final _controller = DrawerMenuController();
  /// 如果_selectedContent是偶数,则显示不带滚动且通过滑动手势打开菜单的页面。
  /// 否则,显示可滚动列表。
  int _selectedContent = 0;
  final double _rightMargin = 70.0;
  final double _menuOverlapWidth = 20;

  [@override](/user/override)
  void initState() {
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return DrawerMenu(
      controller: _controller,
      menu: _buildMenu(),
      body: _buildBody(),
      rightMargin: _rightMargin,
      menuOverlapWidth: _menuOverlapWidth,
      shadowWidth: _rightMargin + _menuOverlapWidth,
      shadowColor: const Color(0x66000000),
      dragMode:
          _selectedContent % 2 != 0 ? DragMode.always : DragMode.onlyFling,
    );
  }

  Widget _buildMenu() {
    final listView = ListView.builder(itemBuilder: (context, index) {
      return InkWell(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text("内容 $index"),
        ),
        onTap: () {
          _controller.close();
          setState(() {
            _selectedContent = index;
          });
        },
      );
    });

    Widget menu = WaveBorder(
        waveWidth: _menuOverlapWidth,
        child: SafeArea(
          child: Material(color: Colors.transparent, child: listView),
        ));

    // 应用状态栏和导航栏主题设置。
    // 如果你想为Android配置透明导航栏(与示例相同),你需要调用以下方法(例如,在initState中):
    // SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
    // 并设置SystemUiOverlayStyle字段:
    // systemNavigationBarColor: Colors.transparent,
    // systemNavigationBarContrastEnforced: false,
    menu = AnnotatedRegion<SystemUiOverlayStyle>(
      value: const SystemUiOverlayStyle(
        // Android部分。
        statusBarColor: Colors.transparent,
        statusBarIconBrightness: Brightness.dark,
        systemNavigationBarColor: Colors.transparent,
        systemNavigationBarContrastEnforced: false,
        systemNavigationBarIconBrightness: Brightness.dark,
        // iOS部分。
        // 当Android设置为暗色时,iOS设置为亮色。奇怪。
        statusBarBrightness: Brightness.light,
      ),
      child: menu,
    );

    return menu;
  }

  Widget _buildBody() {
    // 菜单按钮订阅菜单模式(平板|手机)的变化。
    Widget leadingWidget = ValueListenableBuilder<bool>(
        valueListenable: _controller.isTabletModeNotifier,
        builder: (context, value, _) {
          if (value) {
            return const SizedBox();
          }
          return IconButton(
            icon: const Icon(Icons.menu),
            onPressed: () {
              _controller.open();
            },
          );
        });

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        centerTitle: true,
        leading: leadingWidget,
      ),
      body: _buildContent(context, _selectedContent),
    );
  }

  Widget _buildContent(BuildContext context, int index) {
    /// PageView部分
    Widget pageView = Container(
      color: Colors.black12,
      height: 150,
      child: PageView.builder(
        physics: const ClampingScrollPhysics(),
        itemBuilder: (context, index) => Center(
          child: Text(
            "嵌套PageView\n页面 $index",
            textAlign: TextAlign.center,
          ),
        ),
      ),
    );

    /// 内容部分
    Widget content = Container(
      color: Colors.black.withOpacity(0.05),
      padding: const EdgeInsets.all(16.0),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("内容 $_selectedContent"),
            // 订阅scrollPosition(0-1)
            ValueListenableBuilder<double>(
                valueListenable: _controller.scrollPositionNotifier,
                builder: (context, value, _) {
                  return Text(value.toStringAsFixed(2));
                }),
            // 订阅isOpen
            ValueListenableBuilder<bool>(
                valueListenable: _controller.isOpenNotifier,
                builder: (context, value, _) {
                  return Text(value ? "打开" : "关闭");
                }),
          ],
        ),
      ),
    );

    if (index % 2 == 0) {
      return Column(
        children: [pageView, Expanded(child: content)],
      );
    } else {
      return ListView(
        children: [
          pageView,
          for (int i = 0; i < 100; i++) content,
        ],
      );
    }
  }
}

class WaveBorder extends StatelessWidget {
  final Widget child;
  final double waveWidth;

  const WaveBorder({Key? key, required this.child, required this.waveWidth}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ClipPath(
      clipper: WaveClipper(waveWidth: waveWidth),
      child: Container(
        color: Colors.white,
        child: child,
      ),
    );
  }
}

class WaveClipper extends CustomClipper<Path> {
  final double waveWidth;

  WaveClipper({required this.waveWidth});

  [@override](/user/override)
  Path getClip(Size size) {
    final path = Path()
      ..lineTo(size.width, 0)
      ..quadraticBezierTo(size.width - waveWidth, size.height * 0.25,
          size.width - waveWidth / 2, size.height * 0.5)
      ..quadraticBezierTo(size.width, size.height * 0.75,
          size.width - waveWidth / 2, size.height)
      ..lineTo(0, size.height)
      ..close();
    return path;
  }

  [@override](/user/override)
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}

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

1 回复

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


当然,以下是一个关于如何在Flutter项目中使用flutter_drawer_menu插件来实现侧边栏菜单的示例代码。请注意,这个插件可能并不是官方或广泛使用的插件,因此示例代码是基于假设该插件存在并具有类似功能的情境下编写的。如果flutter_drawer_menu插件的具体API有所不同,请根据官方文档进行调整。

首先,确保在pubspec.yaml文件中添加对flutter_drawer_menu的依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_drawer_menu: ^x.y.z  # 替换为实际版本号

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

接下来是示例代码,展示如何使用flutter_drawer_menu插件来实现侧边栏菜单:

import 'package:flutter/material.dart';
import 'package:flutter_drawer_menu/flutter_drawer_menu.dart'; // 假设插件提供此导入路径

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

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

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Drawer Menu Demo'),
      ),
      body: Center(
        child: Text('Home Page Content'),
      ),
      drawer: FlutterDrawerMenu( // 使用FlutterDrawerMenu插件
        header: DrawerHeader(
          decoration: BoxDecoration(color: Colors.blue),
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text('Drawer Header', style: TextStyle(color: Colors.white)),
          ),
        ),
        menuItems: [
          DrawerMenuItem(
            icon: Icons.home,
            title: 'Home',
            onTap: () {
              Navigator.pop(context); // 关闭抽屉菜单
              // 可以添加跳转到主页的逻辑
            },
          ),
          DrawerMenuItem(
            icon: Icons.settings,
            title: 'Settings',
            onTap: () {
              Navigator.pop(context); // 关闭抽屉菜单
              // 可以添加跳转到设置页的逻辑
            },
          ),
          // 可以添加更多菜单项
        ],
      ),
    );
  }
}

// 假设DrawerMenuItem是插件提供的一个组件,用于表示菜单项
// 如果插件没有提供,可以自己定义一个
class DrawerMenuItem extends StatelessWidget {
  final IconData icon;
  final String title;
  final VoidCallback onTap;

  DrawerMenuItem({required this.icon, required this.title, required this.onTap});

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: Icon(icon),
      title: Text(title),
      onTap: onTap,
    );
  }
}

在这个示例中,我们创建了一个简单的Flutter应用,其中包含一个主屏幕和一个使用FlutterDrawerMenu插件实现的侧边栏菜单。侧边栏菜单包含两个菜单项:“Home”和“Settings”,点击菜单项时会关闭抽屉菜单(通过Navigator.pop(context)),你可以根据需要添加跳转到相应页面的逻辑。

请注意,由于flutter_drawer_menu可能不是一个真实存在的插件,因此上述代码中的FlutterDrawerMenuDrawerMenuItem是基于假设的API设计的。如果flutter_drawer_menu插件的实际API有所不同,请查阅其官方文档并根据实际情况进行调整。如果插件不存在,你可以考虑使用Flutter内置的Drawer组件来实现类似的功能。

回到顶部