Flutter TabBar点击抖动摇晃动画实现问题,角度收缩抖动效果不符合预期

问题修复说明
原有代码核心问题如下:
- 仅对整个Tab做了Z轴旋转,缺失图标点击时的纵向收缩动画,且误将文字也加入了动画范围(演示效果中文字保持静止)
- 抖动的Tween序列仅设置了单次左右摆动,没有多段衰减的抖动过程,和预期效果不符
- 动画曲线选择错误,bounce类曲线不适合模拟快速衰减的左右抖动效果
- 未做动画控制器的重置处理,多次连续点击会出现动画异常
完整修复代码
首先补充全局的函数类型定义,导入依赖:
import 'package:flutter/material.dart'; import 'dart:math' as math; typedef FunctionCaller = void Function(void Function());
MyHomePage 部分
class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin { late TabController _tabController; late void Function() _memberCaller; late void Function() _voucherCaller; late void Function() _rewardCaller; late void Function() _drinksCaller; @override void initState() { super.initState(); _tabController = TabController(length: 4, vsync: this); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _callMethoCaller(int index) { switch (index) { case 0: _memberCaller.call(); break; case 1: _voucherCaller.call(); break; case 2: _rewardCaller.call(); break; case 3: _drinksCaller.call(); break; } } @override Widget build(BuildContext context) { List<Widget> _tabs = [ AnimationTab( iconName: Icons.card_giftcard, label: 'Membership', functionCaller: (void Function() method) { _memberCaller = method; }), AnimationTab( iconName: Icons.confirmation_num_outlined, label: 'Voucher', functionCaller: (void Function() method) { _voucherCaller = method; }), AnimationTab( iconName: Icons.emoji_events_outlined, label: 'Rewards', functionCaller: (void Function() method) { _rewardCaller = method; }), AnimationTab( iconName: Icons.wine_bar_outlined, label: 'Drinks', functionCaller: (void Function() method) { _drinksCaller = method; }), ]; return Scaffold( appBar: AppBar( centerTitle: true, bottom: PreferredSize( preferredSize: const Size.fromHeight(60), child: Container( color: Colors.white, child: TabBar( onTap: _callMethoCaller, controller: _tabController, labelPadding: const EdgeInsets.only(top: 5.0, bottom: 2.0), indicatorColor: Colors.black, tabs: _tabs, ), ), ), ), body: TabBarView( controller: _tabController, children: const [ Center(child: Text('1')), Center(child: Text('2')), Center(child: Text('3')), Center(child: Text('4')), ], ), ); } }
AnimationTab 核心修改部分
class AnimationTab extends StatefulWidget { final String label; final IconData iconName; final FunctionCaller functionCaller; const AnimationTab({ Key? key, required this.iconName, required this.label, required this.functionCaller, }) : super(key: key); @override _AnimationTabState createState() => _AnimationTabState(); } class _AnimationTabState extends State<AnimationTab> with TickerProviderStateMixin { late AnimationController _animationController; late Animation<double> _rotateAnimation; late Animation<double> _scaleAnimation; final _tabTextStyle = const TextStyle( fontWeight: FontWeight.w300, fontSize: 12, color: Colors.black); @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 350), ); // 旋转抖动动画,多段衰减模拟真实抖动效果 _rotateAnimation = TweenSequence<double>([ TweenSequenceItem(tween: Tween(begin: 0, end: 10), weight: 20), TweenSequenceItem(tween: Tween(begin: 10, end: -10), weight: 30), TweenSequenceItem(tween: Tween(begin: -10, end: 5), weight: 30), TweenSequenceItem(tween: Tween(begin: 5, end: -5), weight: 30), TweenSequenceItem(tween: Tween(begin: -5, end: 0), weight: 20), ]).animate(CurvedAnimation(parent: _animationController, curve: Curves.linear)); // 纵向收缩动画 _scaleAnimation = TweenSequence<double>([ TweenSequenceItem(tween: Tween(begin: 1.0, end: 0.7), weight: 25), TweenSequenceItem(tween: Tween(begin: 0.7, end: 1.2), weight: 25), TweenSequenceItem(tween: Tween(begin: 1.2, end: 1.0), weight: 50), ]).animate(CurvedAnimation(parent: _animationController, curve: Curves.linear)); widget.functionCaller.call(_animationExecution); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _animationExecution() { // 点击时先重置动画,避免连续点击异常 if(_animationController.isAnimating) _animationController.stop(); _animationController.reset(); _animationController.forward(); } @override Widget build(BuildContext context) { return Tab( // 仅对图标做动画,文字保持静止 icon: AnimatedBuilder( animation: _animationController, builder: (ctx, child) { return Transform( transform: Matrix4.diagonal3Values(1.0, _scaleAnimation.value, 1.0) ..rotateZ(math.pi * _rotateAnimation.value / 180), alignment: Alignment.center, child: child, ); }, child: Icon(widget.iconName, color: Colors.black), ), child: Text(widget.label, style: _tabTextStyle), ); } }
内容的提问来源于stack exchange,提问作者Phil D
相关产品推荐
相关产品推荐

