You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Flutter默认组件实现Material 3风格NavigationRail响应式子菜单?

实现Material 3风格响应式NavigationRail带子菜单

完全可以用Flutter默认的Material 3组件实现这个效果,核心是结合LayoutBuilder做响应式判断,配合NavigationRail、NavigationDrawer和ListTile构建子菜单容器。下面是具体实现方案:

1. 定义响应式宽度阈值

先设定三个宽度区间的阈值(可根据需求调整):

  • 小屏:宽度 < 600px,使用NavigationDrawer
  • 中等屏:600px ≤ 宽度 < 1000px,选中NavigationRail项时显示子菜单
  • 大屏:宽度 ≥ 1000px,始终显示子菜单

2. 核心布局结构

用LayoutBuilder包裹整体布局,根据当前可用宽度选择对应的导航模式:

class ResponsiveNavRail extends StatefulWidget {
  const ResponsiveNavRail({super.key});

  @override
  State<ResponsiveNavRail> createState() => _ResponsiveNavRailState();
}

class _ResponsiveNavRailState extends State<ResponsiveNavRail> {
  // 当前选中的导航项索引
  int _selectedNavIndex = 0;
  // 控制小屏时Drawer的显示
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  // 导航项数据(包含子菜单)
  final List<NavItem> _navItems = [
    NavItem(
      icon: Icons.home,
      label: '首页',
      subItems: ['概览', '统计', '快捷操作'],
    ),
    NavItem(
      icon: Icons.settings,
      label: '设置',
      subItems: ['账号', '通知', '隐私'],
    ),
    NavItem(
      icon: Icons.person,
      label: '个人中心',
      subItems: [], // 无子菜单的项
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final double width = constraints.maxWidth;
        final bool isSmallScreen = width < 600;
        final bool isMediumScreen = width >= 600 && width < 1000;
        final bool isLargeScreen = width >= 1000;

        return Scaffold(
          key: _scaffoldKey,
          // 小屏显示Drawer按钮
          appBar: isSmallScreen
              ? AppBar(
                  leading: IconButton(
                    icon: const Icon(Icons.menu),
                    onPressed: () => _scaffoldKey.currentState?.openDrawer(),
                  ),
                )
              : null,
          drawer: isSmallScreen
              ? NavigationDrawer(
                  selectedIndex: _selectedNavIndex,
                  onDestinationSelected: (index) {
                    setState(() => _selectedNavIndex = index);
                    Navigator.pop(context);
                  },
                  children: [
                    ..._navItems.map((item) => NavigationDrawerDestination(
                          icon: Icon(item.icon),
                          label: Text(item.label),
                        )),
                    // 子菜单在Drawer里直接展开显示
                    if (_navItems[_selectedNavIndex].subItems.isNotEmpty)
                      const Divider(),
                    ..._navItems[_selectedNavIndex].subItems.map((subItem) => 
                          Padding(
                            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                            child: ListTile(
                              title: Text(subItem),
                              onTap: () {},
                            ),
                          )),
                  ],
                )
              : null,
          body: Row(
            children: [
              // 中大屏显示NavigationRail
              if (!isSmallScreen)
                NavigationRail(
                  selectedIndex: _selectedNavIndex,
                  onDestinationSelected: (index) {
                    setState(() => _selectedNavIndex = index);
                  },
                  labelType: NavigationRailLabelType.all,
                  destinations: _navItems.map((item) => NavigationRailDestination(
                        icon: Icon(item.icon),
                        label: Text(item.label),
                      )).toList(),
                ),
              const VerticalDivider(thickness: 1, width: 1),
              // 主内容区 + 子菜单容器
              Expanded(
                child: Row(
                  children: [
                    // 子菜单容器:大屏始终显示,中等屏仅选中项有子菜单时显示
                    if ((isLargeScreen || 
                         (isMediumScreen && _navItems[_selectedNavIndex].subItems.isNotEmpty)) &&
                        _navItems[_selectedNavIndex].subItems.isNotEmpty)
                      Container(
                        width: 280,
                        decoration: BoxDecoration(
                          border: Border(
                            right: BorderSide(
                              color: Theme.of(context).dividerColor,
                              width: 1,
                            ),
                          ),
                        ),
                        child: ListView.builder(
                          itemCount: _navItems[_selectedNavIndex].subItems.length,
                          itemBuilder: (context, index) {
                            final subItem = _navItems[_selectedNavIndex].subItems[index];
                            return ListTile(
                              title: Text(subItem),
                              onTap: () {},
                              style: ListTileStyle.list,
                            );
                          },
                        ),
                      ),
                    // 主内容区域
                    Expanded(
                      child: Center(
                        child: Text(
                          '当前选中:${_navItems[_selectedNavIndex].label}',
                          style: Theme.of(context).textTheme.titleLarge,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

// 导航项数据模型
class NavItem {
  final IconData icon;
  final String label;
  final List<String> subItems;

  NavItem({required this.icon, required this.label, required this.subItems});
}

3. 关键细节说明

  • 响应式判断:通过LayoutBuilder获取当前布局宽度,自动切换导航模式,完全符合Material 3的响应式要求。
  • 子菜单显示逻辑:
    • 大屏:不管是否选中,只要当前导航项有子菜单就始终显示右侧容器。
    • 中等屏:只有选中的导航项包含子菜单时,才渲染右侧子菜单容器。
    • 小屏:NavigationRail隐藏,改用NavigationDrawer,子菜单跟随选中项展开在Drawer内。
  • Material 3风格适配:使用默认的NavigationRail、ListTile、NavigationDrawer组件,会自动继承当前主题的Material 3样式,无需额外自定义。

4. 自定义调整建议

  • 可根据产品需求修改宽度阈值,比如把大屏阈值调整到1200px。
  • 子菜单的ListTile可添加选中状态、图标等,增强视觉反馈。
  • 可给子菜单容器添加阴影、圆角等Material 3风格的装饰,提升质感。

内容的提问来源于stack exchange,提问作者Daniel Reghin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 00:55:00