如何让 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
相关产品推荐
相关产品推荐

