Skip to content

Material 3 导航组件样式规范

最后更新:2026-01-18 版本:v1.0 状态:✅ 强制执行

💡 提示:本文档是导航组件的详细实战指南。如需查看完整的 Material 3 设计系统规范(包括文本、图标、颜色等基础规范),请参考 Material 3 设计系统


📋 目录

  1. 组件类型选择
  2. AppBar 样式规范
  3. BottomNavigationBar 样式规范
  4. NavigationRail 样式规范
  5. Drawer 样式规范
  6. 代码示例
  7. 常见错误

组件类型选择

决策树

导航类型?
├── 顶部导航栏
│   ├── 标准页面(有返回) → ✅ AppBar + leading: BackButton
│   ├── 主页面(无返回) → ✅ AppBar + leading: null
│   └── 搜索页面 → ✅ AppBar + TextField

├── 底部导航(2-5个主导航)
│   └── ✅ BottomNavigationBar / NavigationBar (Material 3)

├── 侧边导航(5+个导航项)
│   ├── 移动端 → ✅ Drawer
│   └── 平板/桌面端 → ✅ NavigationRail

└── Tab导航(2-6个标签)
    └── ✅ TabBar + TabBarView

组件类型对照表

组件Material 3 组件使用场景示例
顶部导航AppBar页面标题、操作按钮、搜索所有页面顶部
底部导航NavigationBar (M3) / BottomNavigationBar主导航(2-5项)首页、生词本、统计、设置
侧边导航Drawer辅助导航、设置、用户信息侧滑菜单
导航栏NavigationRail平板/桌面侧边导航大屏设备
Tab导航TabBar页面内容切换统计页面内的不同视图

AppBar 样式规范

1. 基础样式

Material 3 默认样式

dart
// ✅ 标准 AppBar
AppBar(
  title: Text('页面标题'),
  // 自动样式:
  // - 背景色:surface
  // - 标题颜色:onSurface
  // - 图标颜色:onSurfaceVariant
)

2. 高度规范

场景高度
标准 AppBar56dp
带搜索的 AppBar56dp
大标题 AppBar112dp (large)
dart
// ✅ 标准高度(默认)
AppBar(
  title: Text('标题'),
)

// ⚠️ 大标题(SliverAppBar)
SliverAppBar(
  expandedHeight: 112,
  title: Text('大标题'),
)

3. 颜色规范

背景色(自动)

dart
// ✅ 正确:使用默认背景色
AppBar(
  title: Text('标题'),
  // 自动使用 colorScheme.surface
)

// ⚠️ 自定义背景色(特殊场景)
AppBar(
  backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
  title: Text('标题'),
)

// ❌ 错误:硬编码颜色
AppBar(
  backgroundColor: Colors.blue,  // 不适配主题
  title: Text('标题'),
)

前景色(自动)

dart
// ✅ 正确:自动使用主题颜色
AppBar(
  title: Text('标题'),
  // 标题颜色:onSurface(自动)
  // 图标颜色:onSurfaceVariant(自动)
)

4. 标题样式

dart
// ✅ 默认标题样式
AppBar(
  title: Text('标题'),
  // 自动使用 titleLarge (22sp, w400)
)

// ⚠️ 自定义标题样式(需要时使用)
AppBar(
  title: Text(
    '标题',
    style: Theme.of(context).textTheme.titleLarge?.copyWith(
      fontWeight: FontWeight.w600,
    ),
  ),
)

5. 操作按钮

返回按钮

dart
// ✅ 自动显示返回按钮(有路由栈时)
AppBar(
  title: Text('详情页'),
  // leading 自动显示 BackButton
)

// ⚠️ 自定义返回按钮
AppBar(
  title: Text('详情页'),
  leading: IconButton(
    icon: Icon(Icons.arrow_back),
    onPressed: () => Navigator.pop(context),
  ),
)

// ⚠️ 隐藏返回按钮
AppBar(
  title: Text('主页'),
  automaticallyImplyLeading: false,
)

右侧操作按钮

dart
// ✅ 单个操作
AppBar(
  title: Text('标题'),
  actions: [
    IconButton(
      icon: Icon(Icons.search),
      onPressed: () {},
    ),
  ],
)

// ✅ 多个操作(最多3个)
AppBar(
  title: Text('标题'),
  actions: [
    IconButton(
      icon: Icon(Icons.search),
      onPressed: () {},
    ),
    IconButton(
      icon: Icon(Icons.filter_list),
      onPressed: () {},
    ),
    IconButton(
      icon: Icon(Icons.more_vert),
      onPressed: () {},
    ),
  ],
)

// ❌ 错误:操作按钮过多(> 3个)
AppBar(
  title: Text('标题'),
  actions: [
    IconButton(...),
    IconButton(...),
    IconButton(...),
    IconButton(...),  // 太多了!
  ],
)

6. 搜索 AppBar

dart
// ✅ 搜索页面 AppBar
AppBar(
  title: TextField(
    autofocus: true,
    decoration: InputDecoration(
      hintText: '搜索书籍...',
      border: InputBorder.none,
      hintStyle: TextStyle(
        color: Theme.of(context).colorScheme.onSurfaceVariant,
      ),
    ),
  ),
  leading: IconButton(
    icon: Icon(Icons.arrow_back),
    onPressed: () => Navigator.pop(context),
  ),
  actions: [
    IconButton(
      icon: Icon(Icons.clear),
      onPressed: () {
        // 清空搜索
      },
    ),
  ],
)

7. SliverAppBar(可折叠)

dart
// ✅ 可折叠 AppBar
CustomScrollView(
  slivers: [
    SliverAppBar(
      expandedHeight: 200,
      pinned: true,
      flexibleSpace: FlexibleSpaceBar(
        title: Text('可折叠标题'),
        background: Image.asset(
          'assets/header.jpg',
          fit: BoxFit.cover,
        ),
      ),
    ),
    SliverList(...),
  ],
)

BottomNavigationBar 样式规范

1. Material 3 NavigationBar(推荐)

dart
// ✅ Material 3 NavigationBar
NavigationBar(
  selectedIndex: _currentIndex,
  onDestinationSelected: (index) {
    setState(() => _currentIndex = index);
  },
  destinations: [
    NavigationDestination(
      icon: Icon(Icons.home_outlined),
      selectedIcon: Icon(Icons.home),
      label: '首页',
    ),
    NavigationDestination(
      icon: Icon(Icons.book_outlined),
      selectedIcon: Icon(Icons.book),
      label: '生词本',
    ),
    NavigationDestination(
      icon: Icon(Icons.bar_chart_outlined),
      selectedIcon: Icon(Icons.bar_chart),
      label: '统计',
    ),
    NavigationDestination(
      icon: Icon(Icons.settings_outlined),
      selectedIcon: Icon(Icons.settings),
      label: '设置',
    ),
  ],
)

2. BottomNavigationBar(兼容)

dart
// ⚠️ Material 2 BottomNavigationBar(兼容旧代码)
BottomNavigationBar(
  currentIndex: _currentIndex,
  onTap: (index) {
    setState(() => _currentIndex = index);
  },
  type: BottomNavigationBarType.fixed,  // 固定模式
  selectedItemColor: Theme.of(context).colorScheme.primary,
  unselectedItemColor: Theme.of(context).colorScheme.onSurfaceVariant,
  items: [
    BottomNavigationBarItem(
      icon: Icon(Icons.home_outlined),
      activeIcon: Icon(Icons.home),
      label: '首页',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.book_outlined),
      activeIcon: Icon(Icons.book),
      label: '生词本',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.bar_chart_outlined),
      activeIcon: Icon(Icons.bar_chart),
      label: '统计',
    ),
  ],
)

3. 导航项数量规范

数量推荐说明
2个⚠️太少,考虑使用 TabBar
3-5个理想数量
5+个太多,使用 Drawer 或 NavigationRail

4. 图标规范

使用 Outlined + Filled 图标对

dart
// ✅ 正确:未选中用 Outlined,选中用 Filled
NavigationDestination(
  icon: Icon(Icons.home_outlined),      // 未选中
  selectedIcon: Icon(Icons.home),       // 选中
  label: '首页',
)

// ❌ 错误:未区分选中状态
NavigationDestination(
  icon: Icon(Icons.home),  // 始终 Filled
  label: '首页',
)

1. 基础用法(平板/桌面)

dart
// ✅ NavigationRail(侧边导航栏)
Row(
  children: [
    NavigationRail(
      selectedIndex: _selectedIndex,
      onDestinationSelected: (index) {
        setState(() => _selectedIndex = index);
      },
      labelType: NavigationRailLabelType.all,  // 显示所有标签
      destinations: [
        NavigationRailDestination(
          icon: Icon(Icons.home_outlined),
          selectedIcon: Icon(Icons.home),
          label: Text('首页'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.book_outlined),
          selectedIcon: Icon(Icons.book),
          label: Text('生词本'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.bar_chart_outlined),
          selectedIcon: Icon(Icons.bar_chart),
          label: Text('统计'),
        ),
      ],
    ),
    VerticalDivider(thickness: 1, width: 1),
    Expanded(
      child: _pages[_selectedIndex],
    ),
  ],
)

2. 标签显示模式

模式说明
NavigationRailLabelType.none不显示标签
NavigationRailLabelType.selected仅选中项显示标签
NavigationRailLabelType.all所有项显示标签(推荐)

Drawer 样式规范

1. 基础样式

dart
// ✅ Drawer 侧滑菜单
Drawer(
  child: ListView(
    padding: EdgeInsets.zero,
    children: [
      // 头部
      DrawerHeader(
        decoration: BoxDecoration(
          color: Theme.of(context).colorScheme.primaryContainer,
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            CircleAvatar(
              radius: 32,
              child: Icon(Icons.person, size: 32),
            ),
            SizedBox(height: 12),
            Text(
              '用户名',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            Text(
              'user@example.com',
              style: Theme.of(context).textTheme.bodySmall,
            ),
          ],
        ),
      ),

      // 菜单项
      ListTile(
        leading: Icon(Icons.home),
        title: Text('首页'),
        onTap: () {
          Navigator.pop(context);
          // 导航逻辑
        },
      ),
      ListTile(
        leading: Icon(Icons.settings),
        title: Text('设置'),
        onTap: () {
          Navigator.pop(context);
          // 导航逻辑
        },
      ),
      Divider(),
      ListTile(
        leading: Icon(Icons.logout),
        title: Text('退出登录'),
        onTap: () {
          Navigator.pop(context);
          // 登出逻辑
        },
      ),
    ],
  ),
)

2. DrawerHeader 样式

dart
// ✅ 使用 primaryContainer 背景色
DrawerHeader(
  decoration: BoxDecoration(
    color: Theme.of(context).colorScheme.primaryContainer,
  ),
  child: ...,
)

// ❌ 错误:硬编码颜色
DrawerHeader(
  decoration: BoxDecoration(
    color: Colors.blue,  // 不适配主题
  ),
  child: ...,
)

代码示例

示例1:完整主页框架(AppBar + BottomNavigationBar)

dart
class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _currentIndex = 0;

  final List<Widget> _pages = [
    HomeTabPage(),
    NotebookTabPage(),
    StatisticsTabPage(),
    SettingsTabPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_getTitle()),
        actions: [
          IconButton(
            icon: Icon(Icons.search),
            onPressed: () {
              // 搜索
            },
          ),
        ],
      ),
      body: _pages[_currentIndex],
      bottomNavigationBar: NavigationBar(
        selectedIndex: _currentIndex,
        onDestinationSelected: (index) {
          setState(() => _currentIndex = index);
        },
        destinations: [
          NavigationDestination(
            icon: Icon(Icons.home_outlined),
            selectedIcon: Icon(Icons.home),
            label: '首页',
          ),
          NavigationDestination(
            icon: Icon(Icons.book_outlined),
            selectedIcon: Icon(Icons.book),
            label: '生词本',
          ),
          NavigationDestination(
            icon: Icon(Icons.bar_chart_outlined),
            selectedIcon: Icon(Icons.bar_chart),
            label: '统计',
          ),
          NavigationDestination(
            icon: Icon(Icons.settings_outlined),
            selectedIcon: Icon(Icons.settings),
            label: '设置',
          ),
        ],
      ),
    );
  }

  String _getTitle() {
    switch (_currentIndex) {
      case 0:
        return '首页';
      case 1:
        return '生词本';
      case 2:
        return '统计';
      case 3:
        return '设置';
      default:
        return '';
    }
  }
}

示例2:带 Drawer 的页面

dart
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Reading Vocab Helper'),
      ),
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: [
            DrawerHeader(
              decoration: BoxDecoration(
                color: Theme.of(context).colorScheme.primaryContainer,
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  CircleAvatar(
                    radius: 32,
                    backgroundColor:
                        Theme.of(context).colorScheme.primary,
                    child: Icon(
                      Icons.person,
                      size: 32,
                      color: Theme.of(context).colorScheme.onPrimary,
                    ),
                  ),
                  SizedBox(height: 12),
                  Text(
                    '学习者',
                    style: Theme.of(context).textTheme.titleLarge?.copyWith(
                          color: Theme.of(context)
                              .colorScheme
                              .onPrimaryContainer,
                        ),
                  ),
                ],
              ),
            ),
            ListTile(
              leading: Icon(Icons.home),
              title: Text('首页'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
            ListTile(
              leading: Icon(Icons.book),
              title: Text('我的书籍'),
              onTap: () {
                Navigator.pop(context);
                // 导航
              },
            ),
            ListTile(
              leading: Icon(Icons.download),
              title: Text('导入/导出'),
              onTap: () {
                Navigator.pop(context);
                // 导航
              },
            ),
            Divider(),
            ListTile(
              leading: Icon(Icons.settings),
              title: Text('设置'),
              onTap: () {
                Navigator.pop(context);
                // 导航
              },
            ),
            ListTile(
              leading: Icon(Icons.help_outline),
              title: Text('帮助'),
              onTap: () {
                Navigator.pop(context);
                // 导航
              },
            ),
          ],
        ),
      ),
      body: Center(
        child: Text('主页内容'),
      ),
    );
  }
}

示例3:TabBar 导航

dart
class StatisticsPage extends StatefulWidget {
  @override
  _StatisticsPageState createState() => _StatisticsPageState();
}

class _StatisticsPageState extends State<StatisticsPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('统计'),
        bottom: TabBar(
          controller: _tabController,
          tabs: [
            Tab(text: '总览'),
            Tab(text: 'CEFR'),
            Tab(text: '趋势'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          OverviewTab(),
          CEFRTab(),
          TrendTab(),
        ],
      ),
    );
  }
}

常见错误

❌ 错误1:AppBar 硬编码颜色

dart
// ❌ 错误:硬编码背景色
AppBar(
  backgroundColor: Colors.blue,
  title: Text('标题'),
)

// ✅ 正确:使用默认颜色
AppBar(
  title: Text('标题'),
  // 自动使用 colorScheme.surface
)

❌ 错误2:底部导航项过多

dart
// ❌ 错误:导航项太多(6个)
NavigationBar(
  destinations: [
    NavigationDestination(...),
    NavigationDestination(...),
    NavigationDestination(...),
    NavigationDestination(...),
    NavigationDestination(...),
    NavigationDestination(...),  // 太多了!
  ],
)

// ✅ 正确:3-5个导航项
NavigationBar(
  destinations: [
    NavigationDestination(...),
    NavigationDestination(...),
    NavigationDestination(...),
    NavigationDestination(...),
  ],
)

❌ 错误3:图标未区分选中状态

dart
// ❌ 错误:未使用 selectedIcon
NavigationDestination(
  icon: Icon(Icons.home),
  label: '首页',
)

// ✅ 正确:使用 Outlined + Filled 图标对
NavigationDestination(
  icon: Icon(Icons.home_outlined),
  selectedIcon: Icon(Icons.home),
  label: '首页',
)

❌ 错误4:AppBar 操作按钮过多

dart
// ❌ 错误:操作按钮超过3个
AppBar(
  title: Text('标题'),
  actions: [
    IconButton(icon: Icon(Icons.search), onPressed: () {}),
    IconButton(icon: Icon(Icons.filter_list), onPressed: () {}),
    IconButton(icon: Icon(Icons.sort), onPressed: () {}),
    IconButton(icon: Icon(Icons.share), onPressed: () {}),
  ],
)

// ✅ 正确:最多3个,多余的放到菜单中
AppBar(
  title: Text('标题'),
  actions: [
    IconButton(icon: Icon(Icons.search), onPressed: () {}),
    IconButton(icon: Icon(Icons.filter_list), onPressed: () {}),
    PopupMenuButton(
      itemBuilder: (context) => [
        PopupMenuItem(child: Text('排序')),
        PopupMenuItem(child: Text('分享')),
      ],
    ),
  ],
)

❌ 错误5:DrawerHeader 硬编码颜色

dart
// ❌ 错误:硬编码背景色
DrawerHeader(
  decoration: BoxDecoration(
    color: Colors.blue,
  ),
  child: ...,
)

// ✅ 正确:使用主题颜色
DrawerHeader(
  decoration: BoxDecoration(
    color: Theme.of(context).colorScheme.primaryContainer,
  ),
  child: ...,
)

快速参考

常用导航组件模板

dart
// AppBar 模板
AppBar(
  title: Text('标题'),
  actions: [
    IconButton(
      icon: Icon(Icons.search),
      onPressed: () {},
    ),
  ],
)

// NavigationBar 模板
NavigationBar(
  selectedIndex: _currentIndex,
  onDestinationSelected: (index) {
    setState(() => _currentIndex = index);
  },
  destinations: [
    NavigationDestination(
      icon: Icon(Icons.home_outlined),
      selectedIcon: Icon(Icons.home),
      label: '首页',
    ),
    // ...更多导航项
  ],
)

// Drawer 模板
Drawer(
  child: ListView(
    padding: EdgeInsets.zero,
    children: [
      DrawerHeader(
        decoration: BoxDecoration(
          color: Theme.of(context).colorScheme.primaryContainer,
        ),
        child: Text('Header'),
      ),
      ListTile(
        leading: Icon(Icons.home),
        title: Text('首页'),
        onTap: () => Navigator.pop(context),
      ),
    ],
  ),
)

相关文档


维护者:Reading Vocab Helper Team 反馈:发现问题请提Issue