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

如何让 widget 沿贝塞尔曲线路径在起点与终点之间实现位移动画?

Flutter Widget沿贝塞尔曲线路径移动实现方案

你需要的同页面内类Hero曲线位移效果无需依赖路由跳转,核心逻辑是通过动画进度实时计算贝塞尔曲线上的坐标,绑定到Widget的位置属性上即可实现。

效果示例图

实现步骤

  • 第一步:定义贝塞尔曲线关键点位
    二阶贝塞尔曲线仅需3个点位即可实现平滑弧度,完全可以满足类Hero的位移效果需求:

    起点P0:Widget的初始位置坐标
    控制点P1:控制曲线弧度,可根据需求调整,要实现抛射效果可以把控制点设在两点连线的垂直方向偏移位置
    终点P2:Widget要移动到的目标位置坐标

  • 第二步:实现贝塞尔坐标计算逻辑
    二阶贝塞尔的坐标计算公式如下,输入0~1的动画进度t即可返回当前时刻的坐标点:
    Offset calculateBezierPoint(double t, Offset p0, Offset p1, Offset p2) {
      double x = pow(1 - t, 2) * p0.dx + 2 * (1 - t) * t * p1.dx + pow(t, 2) * p2.dx;
      double y = pow(1 - t, 2) * p0.dy + 2 * (1 - t) * t * p1.dy + pow(t, 2) * p2.dy;
      return Offset(x, y);
    }
    
  • 第三步:绑定动画更新位置
    借助AnimationController控制动画进度,监听进度变化实时更新Widget的位置即可,完整代码示例如下:
    import 'dart:math';
    import 'package:flutter/material.dart';
    
    class BezierMoveDemo extends StatefulWidget {
      const BezierMoveDemo({super.key});
    
      @override
      State<BezierMoveDemo> createState() => _BezierMoveDemoState();
    }
    
    class _BezierMoveDemoState extends State<BezierMoveDemo> with SingleTickerProviderStateMixin {
      late AnimationController _controller;
      late Animation<double> _animation;
      // 存储当前Widget的坐标
      Offset _currentOffset = const Offset(50, 50);
      // 贝塞尔曲线三个点位
      final Offset p0 = const Offset(50, 50); // 起点
      final Offset p1 = const Offset(200, 0); // 控制点,调整数值可修改曲线弧度
      final Offset p2 = const Offset(300, 500); // 终点
    
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(
          vsync: this,
          duration: const Duration(milliseconds: 600), // 动画时长和系统Hero默认时长一致
        );
        // 用CurvedAnimation实现非线性动画节奏,更接近Hero的自然效果
        _animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
          ..addListener(() {
            setState(() {
              _currentOffset = calculateBezierPoint(_animation.value, p0, p1, p2);
            });
          });
      }
    
      Offset calculateBezierPoint(double t, Offset p0, Offset p1, Offset p2) {
        double x = pow(1 - t, 2) * p0.dx + 2 * (1 - t) * t * p1.dx + pow(t, 2) * p2.dx;
        double y = pow(1 - t, 2) * p0.dy + 2 * (1 - t) * t * p1.dy + pow(t, 2) * p2.dy;
        return Offset(x, y);
      }
    
      // 调用此方法触发动画执行
      void startMove() {
        _controller.reset();
        _controller.forward();
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Stack(
            children: [
              // 要移动的目标Widget
              Positioned.fromRect(
                rect: Rect.fromCenter(center: _currentOffset, width: 50, height: 50),
                child: Container(color: Colors.blue),
              ),
              // 触发动画的按钮
              Positioned(
                bottom: 20,
                left: 20,
                child: ElevatedButton(
                  onPressed: startMove,
                  child: const Text("开始移动"),
                ),
              )
            ],
          ),
        );
      }
    }
    

如果你习惯使用AnimatedPositioned,也可以自定义Tween<Offset>把贝塞尔计算逻辑封装在Tween中,直接赋值给AnimatedPositioned的left/top属性,不需要手动监听动画调用setState。

效果调整说明

  • 如果需要更复杂的曲线,可以改用三阶贝塞尔曲线,新增第二个控制点、对应调整计算公式即可
  • 修改CurvedAnimation的curve参数可以调整动画的快慢节奏,比如用Curves.fastOutSlowIn可以实现和系统Hero完全一致的动画插值效果
  • 调整控制点p1的坐标可以修改曲线的弧度:要上抛效果就把控制点y值设得比起止点都小,要下落效果就设得比起止点都大

内容的提问来源于stack exchange,提问作者Rogerto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 22:45:03