Flutter中实现按钮Hero动画至上方容器的方法求助
同一页面内按钮移动的Hero式动画实现方案
方案1:原生AnimatedPositioned实现(无第三方依赖)
这是最轻量化的原生实现方式,通过状态变量控制按钮位置,配合AnimatedPositioned完成平滑过渡,完全适配Flutter新版本。
核心逻辑:用Stack布局承载按钮和目标容器,通过setState切换按钮的位置状态,AnimatedPositioned自动处理过渡动画。
示例代码:
import 'package:flutter/material.dart'; class ButtonMoveAnimation extends StatefulWidget { const ButtonMoveAnimation({super.key}); @override State<ButtonMoveAnimation> createState() => _ButtonMoveAnimationState(); } class _ButtonMoveAnimationState extends State<ButtonMoveAnimation> { bool _isButtonInTopContainer = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('按钮移动动画')), body: Stack( children: [ // 上方目标容器 Positioned( top: 60, left: 24, child: Container( width: 120, height: 50, decoration: BoxDecoration( border: Border.all(color: Colors.lightBlueAccent), borderRadius: BorderRadius.circular(8), ), child: _isButtonInTopContainer ? _buildActionButton() : const SizedBox.shrink(), ), ), // 可移动的按钮(初始在下方) AnimatedPositioned( duration: const Duration(milliseconds: 350), curve: Curves.easeInOut, top: _isButtonInTopContainer ? 60 : 320, left: 24, child: _isButtonInTopContainer ? const SizedBox.shrink() : _buildActionButton(), ), ], ), ); } Widget _buildActionButton() { return ElevatedButton( onPressed: () => setState(() => _isButtonInTopContainer = !_isButtonInTopContainer), style: ElevatedButton.styleFrom( fixedSize: const Size(120, 50), backgroundColor: Colors.lightBlueAccent, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: Text(_isButtonInTopContainer ? '点击返回' : '点击移动'), ); } }
方案2:利用Hero组件模拟页面内过渡
如果需要复用Hero的共享元素过渡效果(比如阴影、缩放联动),可以通过临时插入透明路由的方式触发Hero动画,实现页面内的Hero效果。
示例代码:
import 'package:flutter/material.dart'; class HeroButtonTransition extends StatefulWidget { const HeroButtonTransition({super.key}); @override State<HeroButtonTransition> createState() => _HeroButtonTransitionState(); } class _HeroButtonTransitionState extends State<HeroButtonTransition> { bool _isButtonMoved = false; void _startHeroAnimation() { Navigator.of(context).push( PageRouteBuilder( opaque: false, transitionDuration: const Duration(milliseconds: 350), pageBuilder: (_, __, ___) => Scaffold( backgroundColor: Colors.transparent, body: Stack( children: [ Positioned( top: 60, left: 24, child: Hero( tag: 'moveable_button', child: _buildActionButton(), ), ), ], ), ), ), ).then((_) => setState(() => _isButtonMoved = true)); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Hero式按钮移动')), body: Stack( children: [ // 上方目标容器 Positioned( top: 60, left: 24, child: Container( width: 120, height: 50, decoration: BoxDecoration( border: Border.all(color: Colors.lightBlueAccent), borderRadius: BorderRadius.circular(8), ), child: _isButtonMoved ? Hero(tag: 'moveable_button', child: _buildActionButton()) : const SizedBox.shrink(), ), ), // 初始位置的按钮 if (!_isButtonMoved) Positioned( top: 320, left: 24, child: Hero( tag: 'moveable_button', child: ElevatedButton( onPressed: _startHeroAnimation, style: ElevatedButton.styleFrom( fixedSize: const Size(120, 50), backgroundColor: Colors.lightBlueAccent, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: const Text('点击移动'), ), ), ), ], ), ); } Widget _buildActionButton() { return ElevatedButton( onPressed: () { if (_isButtonMoved) setState(() => _isButtonMoved = false); }, style: ElevatedButton.styleFrom( fixedSize: const Size(120, 50), backgroundColor: Colors.lightBlueAccent, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: const Text('点击返回'), ); } }
方案3:第三方库flutter_animate实现灵活动画
如果需要更丰富的动画组合(比如移动+缩放+淡入),可以使用flutter_animate库,它适配Flutter新版本,API简洁灵活。
- 先在
pubspec.yaml添加依赖:
dependencies: flutter_animate: ^4.5.0
- 示例代码:
import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; class AnimateButtonMove extends StatefulWidget { const AnimateButtonMove({super.key}); @override State<AnimateButtonMove> createState() => _AnimateButtonMoveState(); } class _AnimateButtonMoveState extends State<AnimateButtonMove> { bool _isMoved = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('按钮移动动画')), body: Stack( children: [ // 上方目标容器 Positioned( top: 60, left: 24, child: Container( width: 120, height: 50, decoration: BoxDecoration( border: Border.all(color: Colors.lightBlueAccent), borderRadius: BorderRadius.circular(8), ), ), ), // 可移动按钮 Positioned( top: _isMoved ? 60 : 320, left: 24, child: ElevatedButton( onPressed: () => setState(() => _isMoved = !_isMoved), style: ElevatedButton.styleFrom( fixedSize: const Size(120, 50), backgroundColor: Colors.lightBlueAccent, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: Text(_isMoved ? '点击返回' : '点击移动'), ).animate().move( duration: 350.ms, curve: Curves.easeInOut, ).scale( begin: const Offset(1, 1), end: const Offset(1, 1), // 如需缩放可修改end值 duration: 350.ms, ), ), ], ), ); } }
选型建议
- 追求轻量化、无依赖:选方案1;
- 需要Hero共享元素的过渡质感:选方案2;
- 需组合多种动画效果:选方案3。
内容的提问来源于stack exchange,提问作者JuanCa
相关产品推荐
相关产品推荐

