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

如何使用Flutter实现如图所示的动画底部导航栏?

Flutter 实现动画底部导航栏

目标效果:点击导航项时,对应图标缩放变色,底部出现高亮背景,页面平滑切换。下面是自定义实现的完整方案:

核心实现代码

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: '动画底部导航栏',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const BottomNavDemo(),
    );
  }
}

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

  @override
  State<BottomNavDemo> createState() => _BottomNavDemoState();
}

class _BottomNavDemoState extends State<BottomNavDemo> {
  int _selectedIndex = 0;
  final PageController _pageController = PageController();

  // 导航项配置:图标、选中颜色、未选中颜色
  final List<Map<String, dynamic>> _navItems = [
    {
      'icon': Icons.home,
      'activeColor': Colors.blue,
      'inactiveColor': Colors.grey
    },
    {
      'icon': Icons.search,
      'activeColor': Colors.orange,
      'inactiveColor': Colors.grey
    },
    {
      'icon': Icons.favorite,
      'activeColor': Colors.red,
      'inactiveColor': Colors.grey
    },
    {
      'icon': Icons.person,
      'activeColor': Colors.purple,
      'inactiveColor': Colors.grey
    },
  ];

  void _onItemTapped(int index) {
    setState(() => _selectedIndex = index);
    _pageController.animateToPage(
      index,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeInOut,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController,
        onPageChanged: (index) => setState(() => _selectedIndex = index),
        children: const [
          HomePage(),
          SearchPage(),
          FavoritePage(),
          ProfilePage(),
        ],
      ),
      bottomNavigationBar: Container(
        height: 65,
        decoration: BoxDecoration(
          color: Colors.white,
          boxShadow: [
            BoxShadow(
              color: Colors.grey.withOpacity(0.3),
              blurRadius: 10,
              offset: const Offset(0, -2),
            ),
          ],
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: List.generate(_navItems.length, (index) {
            bool isSelected = _selectedIndex == index;
            return GestureDetector(
              onTap: () => _onItemTapped(index),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  // 图标缩放动画
                  AnimatedScale(
                    scale: isSelected ? 1.2 : 1.0,
                    duration: const Duration(milliseconds: 250),
                    child: Icon(
                      _navItems[index]['icon'],
                      color: isSelected
                          ? _navItems[index]['activeColor']
                          : _navItems[index]['inactiveColor'],
                      size: 28,
                    ),
                  ),
                  const SizedBox(height: 4),
                  // 底部高亮背景动画
                  AnimatedContainer(
                    width: isSelected ? 24 : 0,
                    height: 4,
                    decoration: BoxDecoration(
                      color: _navItems[index]['activeColor'],
                      borderRadius: BorderRadius.circular(2),
                    ),
                    duration: const Duration(milliseconds: 250),
                  ),
                ],
              ),
            );
          }),
        ),
      ),
    );
  }
}

// 页面示例,保持状态用 AutomaticKeepAliveClientMixin
class HomePage extends StatefulWidget {
  const HomePage({super.key});

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

class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return const Center(child: Text('首页'));
  }
}

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

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return const Center(child: Text('搜索页'));
  }
}

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

  @override
  State<FavoritePage> createState() => _FavoritePageState();
}

class _FavoritePageState extends State<FavoritePage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return const Center(child: Text('收藏页'));
  }
}

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

  @override
  State<ProfilePage> createState() => _ProfilePageState();
}

class _ProfilePageState extends State<ProfilePage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return const Center(child: Text('我的页'));
  }
}

关键实现点说明

  • 动画控制:用AnimatedScale实现图标缩放,AnimatedContainer实现底部高亮条的显示/隐藏过渡,250ms的动画时长保证流畅度。
  • 页面切换:PageController配合PageView实现页面平滑切换,同时监听onPageChanged同步导航栏选中状态。
  • 状态保持:每个页面混入AutomaticKeepAliveClientMixin并设置wantKeepAlive = true,避免切换页面时重新初始化。
  • 自定义样式:底部导航栏用Container包裹添加阴影提升层次感,导航项的颜色和图标可通过_navItems灵活配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 02:01:17