Flutter同一属性实现交错动画问题:自定义缩放动效需求
我明白你想要实现的是按下按钮时按自定义曲线缩小、抬起时按自定义曲线放大的交互,而且不想用reverse()方法,希望每个补间都能独立配置起止值和曲线。你之前用Interval拆分动画的方式确实会带来监听冲突的问题,还得维护额外的状态变量,这里有个更简洁的原生解决方案,完全不需要临时的_animationFuture判断:
核心思路
不再用同一个AnimationController的Interval拆分两个动画,而是每次触发交互时(按下/抬起),先停止当前运行的动画避免冲突,再创建对应需求的独立动画(自定义起止值+曲线),直接绑定到控制器上更新缩放比例。同时新增点击取消的处理逻辑,提升交互完整性。
修改后的完整代码
class AnimatedIconButton extends StatefulWidget { final Widget child; final Function onPress; const AnimatedIconButton({ super.key, required this.child, this.onPress, }) : assert(child != null); @override _AnimatedIconButtonState createState() => _AnimatedIconButtonState(); } class _AnimatedIconButtonState extends State<AnimatedIconButton> with SingleTickerProviderStateMixin { late AnimationController _animationController; double _scale = 1.0; Function get onPress => widget.onPress ?? () {}; void _tapDown(TapDownDetails details) { // 先停止当前动画,防止新旧动画冲突 _animationController.stop(); // 创建缩小动画:从当前缩放值到0.9,使用easeOut曲线 final shrinkAnimation = Tween<double>( begin: _scale, end: 0.9, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeOut, )); shrinkAnimation.addListener(() { setState(() { _scale = shrinkAnimation.value; }); }); // 启动动画,从0开始对应Tween的begin到end的完整过渡 _animationController.forward(from: 0); } void _tapUp(TapUpDetails details) { _animationController.stop(); // 创建放大动画:从当前缩放值到1.0,使用elasticOut曲线 final expandAnimation = Tween<double>( begin: _scale, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.elasticOut, )); expandAnimation.addListener(() { setState(() { _scale = expandAnimation.value; }); }); _animationController.forward(from: 0); // 触发点击回调 onPress(); } void _onTapCancel() { // 处理用户按下后滑动离开按钮的情况,恢复原始缩放比例 _animationController.stop(); final resetAnimation = Tween<double>( begin: _scale, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeOut, )); resetAnimation.addListener(() { setState(() { _scale = resetAnimation.value; }); }); _animationController.forward(from: 0); } @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), // 可根据需求调整动画时长 animationBehavior: AnimationBehavior.preserve, ); } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( onTapDown: _tapDown, onTapUp: _tapUp, onTapCancel: _onTapCancel, // 用AnimatedBuilder优化性能,只有Transform.scale会随动画重建 child: AnimatedBuilder( animation: _animationController, builder: (context, child) { return Transform.scale( scale: _scale, transformHitTests: false, child: child, ); }, child: widget.child, ), ); } }
方案优势
- 无额外状态维护:不需要
_animationFuture这类临时变量,每次触发动画前先停止旧动画,从根源避免了多动画监听冲突的问题。 - 完全自定义动画:每个交互对应的动画都可以独立设置起止值、曲线和时长,完全满足你的需求。
- 更完整的交互逻辑:新增
onTapCancel处理,当用户按下按钮后滑动离开时,按钮会自动恢复原始大小,不会停留在缩小状态。 - 性能优化:通过
AnimatedBuilder包裹缩放组件,只有Transform.scale会随动画重建,避免了整个GestureDetector的不必要重建。
内容的提问来源于stack exchange,提问作者Code Spirit
相关产品推荐
相关产品推荐

