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

如何实现自定义Flutter Bottom Navigation Bar的图标与标签同步高亮效果

解决Flutter底部导航栏选中状态图标与标签同步高亮的问题

我明白你现在的困扰——自定义Flutter底部导航栏时,选中状态下图标能加蓝色边框,但标签没法同步高亮对吧?这其实是因为默认的BottomNavigationBar对item的样式控制比较有限,我们可以通过自定义组件来解决这个问题,下面给你两种可行的实现方案:

方案一:基于BottomNavigationBar的自定义Item组件

这个方案保留系统BottomNavigationBar的基础交互(比如安卓的涟漪点击效果),但通过自定义每个导航项的布局,来同步控制图标边框和标签的高亮状态。

完整代码示例

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Custom Bottom Nav',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

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

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

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

  // 导航项配置数据
  final List<Map<String, dynamic>> _navItems = [
    {'icon': Icons.home, 'label': '首页'},
    {'icon': Icons.search, 'label': '搜索'},
    {'icon': Icons.favorite, 'label': '收藏'},
    {'icon': Icons.person, 'label': '我的'},
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('自定义底部导航栏')),
      body: Center(
        child: Text('当前页面: ${_navItems[_selectedIndex]['label']}'),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
        // 禁用默认的选中/未选中颜色,避免干扰自定义样式
        selectedItemColor: Colors.transparent,
        unselectedItemColor: Colors.transparent,
        items: _navItems.asMap().entries.map((entry) {
          int index = entry.key;
          var item = entry.value;
          return BottomNavigationBarItem(
            icon: CustomNavItem(
              isSelected: _selectedIndex == index,
              icon: Icon(item['icon']),
              label: item['label'],
            ),
            label: '', // 留空,因为自定义组件已包含标签
          );
        }).toList(),
      ),
    );
  }
}

// 自定义导航项组件,统一控制选中/未选中样式
class CustomNavItem extends StatelessWidget {
  final bool isSelected;
  final Icon icon;
  final String label;

  const CustomNavItem({
    super.key,
    required this.isSelected,
    required this.icon,
    required this.label,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      mainAxisSize: MainAxisSize.min,
      children: [
        // 带选中边框的图标容器
        Container(
          padding: const EdgeInsets.all(4),
          decoration: BoxDecoration(
            border: isSelected ? Border.all(color: Colors.blue, width: 2) : null,
            borderRadius: BorderRadius.circular(8),
          ),
          child: Icon(
            icon.icon,
            color: isSelected ? Colors.blue : Colors.grey,
            size: 24,
          ),
        ),
        const SizedBox(height: 4),
        // 同步高亮的标签文本
        Text(
          label,
          style: TextStyle(
            color: isSelected ? Colors.blue : Colors.grey,
            fontSize: 12,
            fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
          ),
        ),
      ],
    );
  }
}

方案说明

  • 我们自定义了CustomNavItem组件,将图标和标签整合在一起,通过isSelected参数统一控制选中状态下的样式:图标添加蓝色边框、图标和标签颜色变为蓝色、标签字体加粗。
  • 把BottomNavigationBar的默认选中/未选中颜色设为透明,避免和自定义样式冲突。

方案二:完全自定义底部导航栏(更灵活)

如果需要更复杂的定制(比如添加徽章、调整整体布局),可以直接用Row+GestureDetector构建完全自定义的底部导航栏,不受系统组件的限制。

完整代码示例

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fully Custom Bottom Nav',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

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

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

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

  final List<Map<String, dynamic>> _navItems = [
    {'icon': Icons.home, 'label': '首页'},
    {'icon': Icons.search, 'label': '搜索'},
    {'icon': Icons.favorite, 'label': '收藏'},
    {'icon': Icons.person, 'label': '我的'},
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('完全自定义底部导航栏')),
      body: Center(
        child: Text('当前页面: ${_navItems[_selectedIndex]['label']}'),
      ),
      // 完全自定义的底部导航容器
      bottomNavigationBar: Container(
        height: 60,
        decoration: BoxDecoration(
          border: Border(top: BorderSide(color: Colors.grey.shade200)),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: _navItems.asMap().entries.map((entry) {
            int index = entry.key;
            var item = entry.value;
            return GestureDetector(
              onTap: () => _onItemTapped(index),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                mainAxisSize: MainAxisSize.min,
                children: [
                  Container(
                    padding: const EdgeInsets.all(4),
                    decoration: BoxDecoration(
                      border: _selectedIndex == index
                          ? Border.all(color: Colors.blue, width: 2)
                          : null,
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Icon(
                      item['icon'],
                      color: _selectedIndex == index ? Colors.blue : Colors.grey,
                      size: 24,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    item['label'],
                    style: TextStyle(
                      color: _selectedIndex == index ? Colors.blue : Colors.grey,
                      fontSize: 12,
                      fontWeight: _selectedIndex == index ? FontWeight.w600 : FontWeight.normal,
                    ),
                  ),
                ],
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

方案说明

  • 直接用Container作为底部导航栏的容器,内部用Row布局每个导航项。
  • 每个项通过GestureDetector处理点击事件,完全控制选中状态下的所有样式细节,适合定制化需求较强的场景。

内容的提问来源于stack exchange,提问作者Pixxu Waku

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 06:49:38