You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 21:58:28