如何在Flutter中实现触摸触发的图片顺时针环绕圆周动画
Flutter 实现圆周环绕图片的顺时针换位动画
核心思路拆解
- 用
Stack+Positioned实现圆周定位:四个图片基于圆心和半径,通过三角函数计算初始位置 - 用
AnimationController+Tween控制动画时长和位置过渡 - 触摸触发时,更新每个图片的目标角度,启动动画平滑过渡
分步实现指南
1. 定义基础参数
先确定圆周的圆心坐标、半径,还有四个图片的初始角度(对应0°、90°、180°、270°的弧度值):
final double centerX = 150; // 圆心X坐标,按需调整 final double centerY = 150; // 圆心Y坐标,按需调整 final double radius = 100; // 圆周半径,按需调整 late List<double> angles; // 存储四个图片的当前角度
2. 初始化动画控制器
在State类中初始化动画控制器,设置动画时长,记得在页面销毁时释放资源:
late AnimationController _controller; @override void initState() { super.initState(); angles = [0, pi/2, pi, 3*pi/2]; // 初始角度对应四个圆周位置 _controller = AnimationController( duration: const Duration(milliseconds: 800), // 动画时长,按需调整 vsync: this, ); } @override void dispose() { _controller.dispose(); // 释放控制器,避免内存泄漏 super.dispose(); }
注意:State类需要混入
SingleTickerProviderStateMixin才能使用vsync: this
3. 动态计算图片位置
写一个方法,根据动画进度计算每个图片的实时位置:
Positioned getImagePosition(int index, double animationValue) { // 计算目标角度:当前角度顺时针加90°(π/2弧度) double startAngle = angles[index]; double targetAngle = startAngle + pi/2; // 动画过程中,从起始角度平滑过渡到目标角度 double currentAngle = startAngle + (targetAngle - startAngle) * animationValue; // Flutter坐标系y轴向下,所以sin结果取负,保证顺时针排列 double x = centerX + radius * cos(currentAngle) - 40; // 40是图片宽高的一半,替换为你图片的实际半宽 double y = centerY - radius * sin(currentAngle) - 40; // 40是图片宽高的一半,替换为你图片的实际半高 return Positioned( left: x, top: y, child: Image.asset( 'assets/i${index+1}.png', // 替换为你的图片路径 width: 80, // 替换为图片实际宽度 height: 80, // 替换为图片实际高度 ), ); }
4. 构建UI与触摸触发
用GestureDetector包裹整个区域,点击时启动动画并更新角度列表:
@override Widget build(BuildContext context) { return GestureDetector( onTap: () { if (!_controller.isAnimating) { // 更新角度:每个角度加π/2,超过2π则取模回到圆周起点 angles = angles.map((angle) => (angle + pi/2) % (2*pi)).toList(); _controller.forward(from: 0); // 从0开始播放动画 } }, child: Stack( children: [ AnimatedBuilder( animation: _controller, builder: (context, child) { return Stack( children: [ getImagePosition(0, _controller.value), getImagePosition(1, _controller.value), getImagePosition(2, _controller.value), getImagePosition(3, _controller.value), ], ); }, ), // 可选:绘制圆心参考点,方便调试 Positioned( left: centerX - 5, top: centerY - 5, child: Container( width: 10, height: 10, color: Colors.red, ), ), ], ), ); }
关键知识点讲解
- 三角函数定位:
cos(angle)计算x轴偏移,sin(angle)计算y轴偏移,由于Flutter y轴向下,取负后才能保证图片按顺时针方向排列在圆周上。 - AnimationController:负责动画的播放、重置与生命周期管理,
vsync用于同步屏幕刷新,避免动画卡顿。 - AnimatedBuilder:仅重建动画相关的Widget,避免整个UI重绘,提升性能。
- 触摸防抖:判断
!_controller.isAnimating防止动画重复触发,保证一次动画完成后才能再次触发切换。
优化提示
- 可以用
MediaQuery.of(context).size动态计算圆心坐标和半径,适配不同屏幕尺寸。 - 加入动画曲线(如
Curves.easeInOut)让过渡更自然,只需给AnimationController套一层CurvedAnimation即可。 - 如果需要循环动画,可以在
_controller.addStatusListener中监听动画完成状态,自动触发下一轮。
内容的提问来源于stack exchange,提问作者Tipu Sultan
相关产品推荐
相关产品推荐

