Flutter组件 - BottomNavigationBar 底部导航栏

📚 目录


简介

BottomNavigationBar 是 Flutter 中最常用的底部导航组件,通常与 Scaffold 配合使用,用于在应用的不同页面之间切换。

适用场景

  • 主页面导航(3-5个主要功能)
  • 社交应用(首页、消息、我的等)
  • 电商应用(首页、分类、购物车、我的)
  • 工具类应用的功能切换

特点

✅ 简单易用,Material Design 风格

✅ 支持图标和文字

✅ 自动处理选中状态

✅ 支持固定和移动两种模式


⚠️ 重要提示

必须包含 main() 函数

所有 Flutter 应用都必须有一个 main() 函数作为入口点,否则会出现以下错误:

复制代码
Error: Undefined name 'main'.
if (entrypoint.main is _UnaryFunction) {
                ^^^^

正确的文件结构:

dart 复制代码
import 'package:flutter/material.dart';

// 1. main() 函数 - 应用入口(必需)
void main() => runApp(MyApp());

// 2. MyApp 类 - 根组件(必需)
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: YourHomePage(), // 你的主页面
    );
  }
}

// 3. 你的页面组件
class YourHomePage extends StatefulWidget {
  // ...
}

❌ 错误示例(缺少 main 函数):

dart 复制代码
import 'package:flutter/material.dart';

// 直接写组件,没有 main() 函数
class MyHomePage extends StatefulWidget {
  // ...
}

✅ 正确示例(完整结构):

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(const MyApp()); // 入口函数,使用 const

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key); // 添加 key 参数
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key); // 添加 key 参数
  
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // ...
}

关于 Key 参数

所有公共 Widget 的构造函数都应该有一个可选的 key 参数,这是 Flutter 的最佳实践:

dart 复制代码
// ✅ 推荐写法
class MyWidget extends StatelessWidget {
  const MyWidget({Key? key}) : super(key: key);
  // ...
}

// ⚠️ 可以运行但不推荐
class MyWidget extends StatelessWidget {
  // 缺少 key 参数
}

为什么需要 key 参数?

  • 帮助 Flutter 识别和管理 Widget
  • 在列表中保持 Widget 状态
  • 提高性能和可维护性

基本用法

最简单的示例

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _currentIndex = 0; // 当前选中的索引

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('底部导航栏示例'),
      ),
      body: Center(
        child: Text(
          '当前页面: $_currentIndex',
          style: const TextStyle(fontSize: 24),
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex, // 当前选中项
        onTap: (index) {
          setState(() {
            _currentIndex = index; // 更新选中项
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: '搜索',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

核心属性

1. items(必需)

导航项列表,类型为 List<BottomNavigationBarItem>

dart 复制代码
items: [
  BottomNavigationBarItem(
    icon: Icon(Icons.home),           // 图标
    activeIcon: Icon(Icons.home),     // 选中时的图标(可选)
    label: '首页',                     // 文字标签
    backgroundColor: Colors.blue,      // 背景色(仅在 shifting 模式下有效)
    tooltip: '首页',                   // 长按提示(可选)
  ),
]

2. currentIndex

当前选中项的索引,从 0 开始

dart 复制代码
currentIndex: 0  // 默认选中第一项

3. onTap

点击事件回调

dart 复制代码
onTap: (int index) {
  print('点击了第 $index 项');
  setState(() {
    _currentIndex = index;
  });
}

4. type

导航栏类型

dart 复制代码
type: BottomNavigationBarType.fixed,    // 固定模式(默认,3-5项时推荐)
type: BottomNavigationBarType.shifting, // 移动模式(4项以上时自动启用)

区别:

  • fixed: 所有项固定显示,宽度相等
  • shifting: 选中项会放大,未选中项缩小

5. 颜色相关

dart 复制代码
backgroundColor: Colors.white,           // 背景色
selectedItemColor: Colors.blue,          // 选中项颜色
unselectedItemColor: Colors.grey,        // 未选中项颜色

6. 图标和文字大小

dart 复制代码
selectedFontSize: 14.0,      // 选中项文字大小
unselectedFontSize: 12.0,    // 未选中项文字大小
iconSize: 24.0,              // 图标大小

7. 显示标签

dart 复制代码
showSelectedLabels: true,    // 是否显示选中项的标签
showUnselectedLabels: true,  // 是否显示未选中项的标签

8. 高度和边距

dart 复制代码
elevation: 8.0,              // 阴影高度

实战案例

案例1:完整的多页面导航

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MainPage(),
    );
  }
}

class MainPage extends StatefulWidget {
  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int _currentIndex = 0;

  // 页面列表
  final List<Widget> _pages = [
    HomePage(),
    CategoryPage(),
    CartPage(),
    ProfilePage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex], // 根据索引显示不同页面
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.grey,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.category),
            label: '分类',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.shopping_cart),
            label: '购物车',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

// 首页
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('首页')),
      body: Center(
        child: Text('首页内容', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

// 分类页
class CategoryPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('分类')),
      body: Center(
        child: Text('分类内容', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

// 购物车页
class CartPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('购物车')),
      body: Center(
        child: Text('购物车内容', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

// 我的页面
class ProfilePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('我的')),
      body: Center(
        child: Text('个人中心', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

案例2:带徽章的导航栏(消息提示)

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: BadgeBottomNavBar(),
    );
  }
}

class BadgeBottomNavBar extends StatefulWidget {
  @override
  _BadgeBottomNavBarState createState() => _BadgeBottomNavBarState();
}

class _BadgeBottomNavBarState extends State<BadgeBottomNavBar> {
  int _currentIndex = 0;
  int _messageCount = 5; // 消息数量

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('带徽章的导航栏')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('当前页面: $_currentIndex', style: TextStyle(fontSize: 24)),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _messageCount++;
                });
              },
              child: Text('增加消息数'),
            ),
          ],
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
            if (index == 1) {
              _messageCount = 0; // 点击消息页时清空徽章
            }
          });
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.blue,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: _buildBadgeIcon(
              Icons.message,
              _messageCount,
            ),
            label: '消息',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }

  // 构建带徽章的图标
  Widget _buildBadgeIcon(IconData icon, int count) {
    return Stack(
      children: [
        Icon(icon),
        if (count > 0)
          Positioned(
            right: 0,
            top: 0,
            child: Container(
              padding: EdgeInsets.all(2),
              decoration: BoxDecoration(
                color: Colors.red,
                borderRadius: BorderRadius.circular(10),
              ),
              constraints: BoxConstraints(
                minWidth: 16,
                minHeight: 16,
              ),
              child: Text(
                count > 99 ? '99+' : '$count',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                ),
                textAlign: TextAlign.center,
              ),
            ),
          ),
      ],
    );
  }
}

案例3:自定义样式的导航栏

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: CustomStyledNavBar(),
    );
  }
}

class CustomStyledNavBar extends StatefulWidget {
  @override
  _CustomStyledNavBarState createState() => _CustomStyledNavBarState();
}

class _CustomStyledNavBarState extends State<CustomStyledNavBar> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('自定义样式导航栏'),
        backgroundColor: Colors.deepPurple,
      ),
      body: Center(
        child: Text(
          '当前页面: $_currentIndex',
          style: TextStyle(fontSize: 24),
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        backgroundColor: Colors.deepPurple[50],
        selectedItemColor: Colors.deepPurple,
        unselectedItemColor: Colors.grey,
        selectedFontSize: 14,
        unselectedFontSize: 12,
        iconSize: 28,
        elevation: 10,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home_outlined),
            activeIcon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.explore_outlined),
            activeIcon: Icon(Icons.explore),
            label: '发现',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.favorite_outline),
            activeIcon: Icon(Icons.favorite),
            label: '收藏',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person_outline),
            activeIcon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

案例4:Shifting 模式(移动模式)

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ShiftingNavBar(),
    );
  }
}

class ShiftingNavBar extends StatefulWidget {
  @override
  _ShiftingNavBarState createState() => _ShiftingNavBarState();
}

class _ShiftingNavBarState extends State<ShiftingNavBar> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Shifting 模式')),
      body: Center(
        child: Text(
          '当前页面: $_currentIndex',
          style: TextStyle(fontSize: 24),
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.shifting,
        selectedItemColor: Colors.white,
        unselectedItemColor: Colors.white70,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
            backgroundColor: Colors.blue,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: '商务',
            backgroundColor: Colors.green,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            label: '学习',
            backgroundColor: Colors.orange,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: '设置',
            backgroundColor: Colors.red,
          ),
        ],
      ),
    );
  }
}

案例5:只显示图标(隐藏文字)

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: IconOnlyNavBar(),
    );
  }
}

class IconOnlyNavBar extends StatefulWidget {
  @override
  _IconOnlyNavBarState createState() => _IconOnlyNavBarState();
}

class _IconOnlyNavBarState extends State<IconOnlyNavBar> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('只显示图标')),
      body: Center(
        child: Text(
          '当前页面: $_currentIndex',
          style: TextStyle(fontSize: 24),
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.grey,
        showSelectedLabels: false,   // 隐藏选中项文字
        showUnselectedLabels: false, // 隐藏未选中项文字
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home, size: 30),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search, size: 30),
            label: '搜索',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.add_circle, size: 35),
            label: '发布',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.notifications, size: 30),
            label: '通知',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person, size: 30),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

案例6:使用 PageView 实现滑动切换

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: PageViewNavBar(),
    );
  }
}

class PageViewNavBar extends StatefulWidget {
  @override
  _PageViewNavBarState createState() => _PageViewNavBarState();
}

class _PageViewNavBarState extends State<PageViewNavBar> {
  int _currentIndex = 0;
  PageController _pageController = PageController();

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController,
        onPageChanged: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        children: [
          _buildPage('首页', Colors.blue[100]!),
          _buildPage('分类', Colors.green[100]!),
          _buildPage('购物车', Colors.orange[100]!),
          _buildPage('我的', Colors.purple[100]!),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          _pageController.animateToPage(
            index,
            duration: Duration(milliseconds: 300),
            curve: Curves.easeInOut,
          );
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.blue,
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.category), label: '分类'),
          BottomNavigationBarItem(icon: Icon(Icons.shopping_cart), label: '购物车'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
        ],
      ),
    );
  }

  Widget _buildPage(String title, Color color) {
    return Container(
      color: color,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              title,
              style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 20),
            Text(
              '左右滑动可以切换页面',
              style: TextStyle(fontSize: 16, color: Colors.grey[600]),
            ),
          ],
        ),
      ),
    );
  }
}

常见问题

0. ❌ Error: Undefined name 'main'

错误信息:

复制代码
org-dartlang-app:/web_entrypoint.dart:21:22: Error: Undefined name 'main'.
if (entrypoint.main is _UnaryFunction) {
                ^^^^

原因: main.dart 文件中缺少 main() 函数

解决方案: 确保你的 main.dart 文件包含完整的结构

dart 复制代码
import 'package:flutter/material.dart';

// ✅ 必须有 main() 函数
void main() => runApp(MyApp());

// ✅ 必须有 MaterialApp
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: YourHomePage(),
    );
  }
}

// 然后才是你的页面
class YourHomePage extends StatefulWidget {
  @override
  _YourHomePageState createState() => _YourHomePageState();
}

class _YourHomePageState extends State<YourHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ...
    );
  }
}

1. 为什么点击导航栏没有反应?

原因: 没有在 onTap 中调用 setState 更新 currentIndex

dart 复制代码
// ❌ 错误写法
onTap: (index) {
  _currentIndex = index; // 没有 setState
}

// ✅ 正确写法
onTap: (index) {
  setState(() {
    _currentIndex = index;
  });
}

2. 如何实现中间凸起的按钮?

使用 FloatingActionButton 配合 floatingActionButtonLocation

dart 复制代码
Scaffold(
  body: _pages[_currentIndex],
  floatingActionButton: FloatingActionButton(
    onPressed: () {
      // 处理中间按钮点击
    },
    child: Icon(Icons.add),
  ),
  floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
  bottomNavigationBar: BottomAppBar(
    shape: CircularNotchedRectangle(), // 凹槽形状
    notchMargin: 8.0,
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        IconButton(icon: Icon(Icons.home), onPressed: () {}),
        IconButton(icon: Icon(Icons.search), onPressed: () {}),
        SizedBox(width: 40), // 中间留空
        IconButton(icon: Icon(Icons.favorite), onPressed: () {}),
        IconButton(icon: Icon(Icons.person), onPressed: () {}),
      ],
    ),
  ),
)

3. 如何保持页面状态不被销毁?

使用 AutomaticKeepAliveClientMixin

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

class _HomePageState extends State<HomePage> 
    with AutomaticKeepAliveClientMixin {
  
  @override
  bool get wantKeepAlive => true; // 保持状态
  
  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用
    return Scaffold(
      appBar: AppBar(title: Text('首页')),
      body: Center(child: Text('首页内容')),
    );
  }
}

4. 导航栏超过5项怎么办?

Material Design 建议最多5项,超过建议使用 Drawer(抽屉)或 TabBar

dart 复制代码
// 如果确实需要更多项,可以设置 type
type: BottomNavigationBarType.shifting,

5. 如何自定义导航栏高度?

BottomNavigationBar 高度是固定的,如需自定义可使用 BottomAppBar

dart 复制代码
bottomNavigationBar: BottomAppBar(
  height: 80, // 自定义高度
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceAround,
    children: [
      // 自定义内容
    ],
  ),
)

最佳实践

1. 导航项数量建议

  • 3-5项:最佳体验
  • 少于3项:考虑使用 TabBar
  • 多于5项:考虑使用 Drawer 或分组

2. 图标选择

  • 使用语义清晰的图标
  • 选中和未选中状态要有明显区别
  • 建议使用 Material Icons
dart 复制代码
// 推荐:使用 outlined 和 filled 版本
icon: Icon(Icons.home_outlined),
activeIcon: Icon(Icons.home),

3. 颜色搭配

  • 选中颜色应与应用主题色一致
  • 未选中颜色使用灰色系
  • 背景色保持简洁
dart 复制代码
selectedItemColor: Theme.of(context).primaryColor,
unselectedItemColor: Colors.grey[600],
backgroundColor: Colors.white,

4. 文字标签

  • 简短明了(2-4个字)
  • 避免使用长文本
  • 可以根据需要隐藏未选中项的标签
dart 复制代码
showSelectedLabels: true,
showUnselectedLabels: false, // 只显示选中项文字

5. 性能优化

  • 使用 const 构造函数
  • 避免在 build 方法中创建新的 List
dart 复制代码
// ✅ 推荐:在类中定义
final List<Widget> _pages = const [
  HomePage(),
  CategoryPage(),
  ProfilePage(),
];

// ❌ 不推荐:每次 build 都创建
@override
Widget build(BuildContext context) {
  final pages = [HomePage(), CategoryPage()]; // 每次都创建新对象
}

6. 状态管理

对于复杂应用,建议使用状态管理方案(Provider、Riverpod、Bloc等)

dart 复制代码
// 使用 Provider 示例
class NavigationProvider extends ChangeNotifier {
  int _currentIndex = 0;
  
  int get currentIndex => _currentIndex;
  
  void setIndex(int index) {
    _currentIndex = index;
    notifyListeners();
  }
}

完整示例:仿微信底部导航

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(WeChatApp());

class WeChatApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Color(0xFF07C160),
      ),
      home: WeChatHome(),
    );
  }
}

class WeChatHome extends StatefulWidget {
  @override
  _WeChatHomeState createState() => _WeChatHomeState();
}

class _WeChatHomeState extends State<WeChatHome> {
  int _currentIndex = 0;
  int _messageCount = 3;

  final List<Widget> _pages = [
    ChatListPage(),
    ContactsPage(),
    DiscoverPage(),
    ProfilePage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: _pages,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Color(0xFF07C160),
        unselectedItemColor: Colors.grey,
        selectedFontSize: 12,
        unselectedFontSize: 12,
        items: [
          BottomNavigationBarItem(
            icon: _buildBadgeIcon(Icons.chat_bubble_outline, _messageCount),
            activeIcon: Icon(Icons.chat_bubble),
            label: '微信',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.contacts_outlined),
            activeIcon: Icon(Icons.contacts),
            label: '通讯录',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.explore_outlined),
            activeIcon: Icon(Icons.explore),
            label: '发现',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person_outline),
            activeIcon: Icon(Icons.person),
            label: '我',
          ),
        ],
      ),
    );
  }

  Widget _buildBadgeIcon(IconData icon, int count) {
    return Stack(
      clipBehavior: Clip.none,
      children: [
        Icon(icon),
        if (count > 0)
          Positioned(
            right: -8,
            top: -4,
            child: Container(
              padding: EdgeInsets.all(4),
              decoration: BoxDecoration(
                color: Colors.red,
                shape: BoxShape.circle,
              ),
              constraints: BoxConstraints(minWidth: 18, minHeight: 18),
              child: Text(
                count > 99 ? '...' : '$count',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                ),
                textAlign: TextAlign.center,
              ),
            ),
          ),
      ],
    );
  }
}

class ChatListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('微信'),
        backgroundColor: Color(0xFFEDEDED),
        foregroundColor: Colors.black,
      ),
      body: Center(child: Text('聊天列表', style: TextStyle(fontSize: 24))),
    );
  }
}

class ContactsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('通讯录'),
        backgroundColor: Color(0xFFEDEDED),
        foregroundColor: Colors.black,
      ),
      body: Center(child: Text('通讯录', style: TextStyle(fontSize: 24))),
    );
  }
}

class DiscoverPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('发现'),
        backgroundColor: Color(0xFFEDEDED),
        foregroundColor: Colors.black,
      ),
      body: Center(child: Text('发现', style: TextStyle(fontSize: 24))),
    );
  }
}

class ProfilePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('我'),
        backgroundColor: Color(0xFFEDEDED),
        foregroundColor: Colors.black,
      ),
      body: Center(child: Text('个人中心', style: TextStyle(fontSize: 24))),
    );
  }
}

总结

BottomNavigationBar 是 Flutter 中最常用的导航组件之一,掌握它的使用对开发移动应用至关重要。

关键要点:

  1. 必须配合 StatefulWidget 使用
  2. 通过 currentIndexonTap 控制选中状态
  3. 建议使用 3-5 个导航项
  4. 注意性能优化和状态保持
  5. 可以通过自定义实现更复杂的效果

希望这份文档能帮助你快速掌握 BottomNavigationBar 的使用!🎉

相关推荐
时光慢煮1 小时前
行旅迹 · 基于 Flutter × OpenHarmony 的旅行记录应用— 构建高体验旅行记录列表视图的跨端实践
flutter·华为·开源·openharmony
hxjhnct1 小时前
CSS 伪类和伪元素
前端·javascript·css
666HZ6661 小时前
数据结构3.0 栈、队列和数组
开发语言·数据结构·算法
bing.shao1 小时前
Golang 在OPC领域的应用
开发语言·后端·golang
❆VE❆1 小时前
【css】打造倾斜异形按钮:CSS radial-gradient 与抗锯齿实战解析
前端·javascript·css
IT陈图图2 小时前
Flutter × OpenHarmony 跨端汇率转换:常用货币对构建与实现解析
flutter·鸿蒙·openharmony
时光慢煮2 小时前
行走的记忆卡片:基于 Flutter × OpenHarmony 的旅行记录应用实践——单个旅行记录卡片构建详解
flutter·华为·开源·openharmony
小白阿龙2 小时前
鸿蒙+flutter 跨平台开发——智力迷宫挑战的实现
flutter·游戏·华为·harmonyos·鸿蒙
a努力。2 小时前
得物Java面试被问:Netty的ByteBuf引用计数和内存释放
java·开发语言·分布式·python·面试·职场和发展