Flutter BottomNavigationBar非选中项动画异常问题
嘿,我完全懂你遇到的这个糟心问题——每次点底部导航栏的一个选项,它自己的动画正常播了,但其他所有没选中的图标都跟着跑一遍关闭动画,确实很影响体验。咱们来搞定它!
问题根源
你现在的写法是直接把Lottie实例作为activeIcon和icon传给BottomNavigationBarItem,每次currentIndex变化时,整个底部导航栏的所有项都会重建。这就导致每个非选中项的icon(也就是你的off动画)都会重新初始化并从头播放一遍,不管它之前有没有被激活过。
解决方案:用状态管理控制动画播放时机
我们可以自定义一个带状态的导航项组件,用Lottie的动画控制器来精准控制什么时候播放动画——只有当某个项从选中状态切换到非选中状态时,才播放它的off动画;初始未选中的项直接保持静态,不播放动画。
步骤1:自定义带状态的导航项组件
class NavItemWidget extends StatefulWidget { final String assetOn; final String assetOff; final String label; final bool isSelected; const NavItemWidget({ super.key, required this.assetOn, required this.assetOff, required this.label, required this.isSelected, }); @override State<NavItemWidget> createState() => _NavItemWidgetState(); } class _NavItemWidgetState extends State<NavItemWidget> with TickerProviderStateMixin { late final LottieAnimationController _onController; late final LottieAnimationController _offController; bool _previousSelectedState = false; @override void initState() { super.initState(); // 初始化两个动画控制器 _onController = LottieAnimationController(vsync: this); _offController = LottieAnimationController(vsync: this); _previousSelectedState = widget.isSelected; // 初始状态处理:选中则播放on动画,未选中则停在off动画的结尾(避免播放) if (widget.isSelected) { _onController.play(); } else { _offController.value = 1.0; } } @override void didUpdateWidget(covariant NavItemWidget oldWidget) { super.didUpdateWidget(oldWidget); // 只有当选中状态发生变化时,才触发对应动画 if (widget.isSelected != _previousSelectedState) { if (widget.isSelected) { // 切换为选中:播放on动画,重置off动画 _offController.reset(); _onController.play(from: 0); } else { // 切换为未选中:播放off动画,重置on动画 _onController.reset(); _offController.play(from: 0); } _previousSelectedState = widget.isSelected; } } @override void dispose() { // 记得销毁控制器,避免内存泄漏 _onController.dispose(); _offController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BottomNavigationBarItem( activeIcon: Lottie.asset( widget.assetOn, width: 20, height: 20, controller: _onController, repeat: false, frameRate: FrameRate.max, ), icon: Lottie.asset( widget.assetOff, width: 20, height: 20, controller: _offController, repeat: false, frameRate: FrameRate.max, ), label: tr(widget.label), ); } }
步骤2:修改原BottomNavigationBar的items部分
把原来的getNavItem调用换成我们自定义的组件,同时传入当前项是否被选中的状态:
BottomNavigationBar( currentIndex: navIndex ?? 0, onTap: (final index) { ref.read(bottomNavProvider.notifier).setAndPersistValue(index); }, type: BottomNavigationBarType.fixed, elevation: 0, backgroundColor: Colors.transparent, showSelectedLabels: false, showUnselectedLabels: false, items: <BottomNavigationBarItem>[ NavItemWidget( assetOn: 'assets/anim/home.json', assetOff: 'assets/anim/home_off.json', label: 'bottom_nav_home', isSelected: navIndex == 0, ), NavItemWidget( assetOn: 'assets/anim/tasks.json', assetOff: 'assets/anim/tasks_off.json', label: 'bottom_nav_tasks', isSelected: navIndex == 1, ), NavItemWidget( assetOn: 'assets/anim/subjects.json', assetOff: 'assets/anim/subjects_off.json', label: 'bottom_nav_reports', isSelected: navIndex == 2, ), NavItemWidget( assetOn: 'assets/anim/profile.json', assetOff: 'assets/anim/profile_off.json', label: 'bottom_nav_settings', isSelected: navIndex == 3, ), ], ),
原理说明
通过自定义的NavItemWidget,我们可以跟踪每个导航项的选中状态变化:
- 初始加载时,未选中的项直接把off动画的控制器设为结尾值,不会播放动画
- 只有当某个项的选中状态从true变成false时,才会触发off动画播放
- 切换到选中状态时,触发on动画播放,同时重置另一个动画的状态
这样就完美解决了所有非选中项乱播动画的问题啦!
备注:内容来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

