Flutter中如何为两个不同尺寸的Widget交换添加动画效果
Flutter实现不同尺寸Text组件的平滑交换动画
需求说明
需要实现两个不同尺寸的Text组件交换功能:点击下方的文本区域触发交换操作,交换时文本的位置和尺寸要同步平滑过渡。之前尝试用AnimatedPositioned,但因为两个文本尺寸差异大,动画效果很怪异。参考动画效果为:上方大尺寸数字与下方小尺寸数字交换位置,同时各自切换到对方的字体大小,整个过程自然流畅。
原示例代码
class SwapWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( height: 80, child: FittedBox( fit: BoxFit.scaleDown, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: const [ Text( "\$", style: TextStyle( fontSize: 40, fontWeight: FontWeight.w400, ), ), Text( "400", style: TextStyle( fontSize: 80, fontWeight: FontWeight.w500, height: 1, ), ), ], ), ), ), ], ), const SizedBox(height: 20), GestureDetector( onTap: () {}, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ Text( '2345', style: TextStyle( fontSize: 20, fontWeight: FontWeight.normal, ), ), Icon(Icons.swap_vert), ], ), ), ], ); } }
解决方案
改用StatefulWidget管理状态,结合AnimationController和Transform组件同时控制位置偏移与缩放动画,解决尺寸差异导致的动画异常问题。
修改后的完整代码
class SwapWidget extends StatefulWidget { @override State<SwapWidget> createState() => _SwapWidgetState(); } class _SwapWidgetState extends State<SwapWidget> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _positionAnim; late Animation<double> _topScaleAnim; late Animation<double> _bottomScaleAnim; String _topNum = "400"; String _bottomNum = "2345"; bool _swapped = false; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); // 控制上下偏移的动画 _positionAnim = Tween<double>(begin: 0, end: 1).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); // 上方文本从大到小的缩放动画(80→20,比例0.25) _topScaleAnim = Tween<double>(begin: 1, end: 0.25).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); // 下方文本从小到大的缩放动画(20→80,比例4) _bottomScaleAnim = Tween<double>(begin: 0.25, end: 1).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); } void _triggerSwap() { if (_controller.isAnimating) return; setState(() { _swapped = !_swapped; // 交换文本内容 final temp = _topNum; _topNum = _bottomNum; _bottomNum = temp; }); // 根据状态正向/反向执行动画 _swapped ? _controller.forward() : _controller.reverse(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { const double topTextSize = 80; const double bottomTextSize = 20; const double swapOffset = 100; // 上下交换的距离 return Column( children: [ SizedBox( height: 80, child: AnimatedBuilder( animation: _controller, builder: (context, child) { return Stack( alignment: Alignment.centerLeft, children: [ // $符号固定位置,不参与动画 const Text( "\$", style: TextStyle( fontSize: 40, fontWeight: FontWeight.w400, ), ), // 原上方数字的动画层 Transform.translate( offset: Offset(40, _positionAnim.value * swapOffset), child: Transform.scale( scale: _topScaleAnim.value, child: Text( _swapped ? _bottomNum : _topNum, style: const TextStyle( fontSize: topTextSize, fontWeight: FontWeight.w500, height: 1, ), ), ), ), // 原下方数字的动画层 Transform.translate( offset: Offset(40, (1 - _positionAnim.value) * -swapOffset), child: Transform.scale( scale: _bottomScaleAnim.value, child: Text( _swapped ? _topNum : _bottomNum, style: const TextStyle( fontSize: bottomTextSize, fontWeight: FontWeight.normal, ), ), ), ), ], ); }, ), ), const SizedBox(height: 20), GestureDetector( onTap: _triggerSwap, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // 下方文本随动画隐藏/显示 Opacity( opacity: _swapped ? 0 : 1, child: Text( _bottomNum, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.normal, ), ), ), const Icon(Icons.swap_vert), ], ), ), ], ); } }
关键说明
- 用
AnimationController统一控制动画时长,搭配CurvedAnimation实现自然的缓动效果 - 通过
Transform.translate控制两个文本的上下位置偏移,Transform.scale同步匹配字体大小的缩放比例 - 利用
Stack将固定的$符号与两个动画文本层叠加,保证布局结构稳定 - 点击触发时先交换文本内容,再执行动画,同时判断动画状态避免重复触发
内容的提问来源于stack exchange,提问作者NoQuesAsked
相关产品推荐
相关产品推荐

