Flutter指南针物理动画实现:SpringSimulation参数调优求助
指南针物理模拟动画参数调整方案
我希望让CustomPaint中的PathL1实现类似指南针摆动的物理模拟动画(目标效果:指针从初始位置摆动到目标角度,伴随自然的阻尼震荡),但找不到合适的SpringSimulation参数(mass、stiffness、damping等)以及AnimationController的配置,以下是我的代码:
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/physics.dart'; class Trace extends StatefulWidget { const Trace({Key? key}) : super(key: key); @override State<Trace> createState() => _TraceState(); } @override class _TraceState extends State<Trace> with TickerProviderStateMixin { late AnimationController animation; late SpringSimulation simulation; Duration duration = const Duration(seconds: 1); @override void initState() { super.initState(); animation = AnimationController( vsync: this, duration: duration, lowerBound: 0, upperBound: pi, )..addListener(() { setState(() {}); }); animation.forward(); simulation = SpringSimulation( const SpringDescription(mass: 0.5, stiffness: 10, damping: 500), 0, 50, 0, ); animation.animateWith(simulation); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: CustomPaint( painter: TracePainter(animation), child: Container(), ), ); } } class TracePainter extends CustomPainter { TracePainter(this.animation); final Animation animation; @override void paint(Canvas canvas, Size size) { double w = size.width; double h = size.height; final paintcii = Paint() ..color = Colors.grey ..style = PaintingStyle.stroke ..strokeWidth = 4; final paintc = Paint()..color = Colors.grey.withOpacity(0.3); canvas.drawLine( Offset(size.width / 2, size.height), Offset(size.width / 2, 0), paintc); canvas.drawLine(Offset(0, size.height / 2), Offset(size.width, size.height / 2), paintc); final pathL1 = Path() ..moveTo(w / 2 - 35 * cos(animation.value), (h / 2) - 35 * sin(animation.value)) ..lineTo(w / 2 + 35 * cos(animation.value), (h / 2) + 35 * sin(animation.value)); canvas.drawPath(pathL1, paintcii); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } }
当前效果:指针直接生硬地移动到目标位置,没有自然的摆动震荡效果。
核心问题分析
- AnimationController配置冲突:手动设置
duration和upperBound: pi会限制弹簧运动的范围和时长,SpringSimulation会自主控制动画节奏,无需这些限制。 - SpringSimulation参数错误:目标值设为
50不符合角度逻辑,指南针角度需用弧度值;mass、stiffness、damping参数搭配不合理,导致没有自然摆动效果。 - 动画调用重复:同时调用
animation.forward()和animation.animateWith(simulation)会导致冲突,后者会覆盖前者的动画逻辑。
修改后的完整代码
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/physics.dart'; class Trace extends StatefulWidget { const Trace({Key? key}) : super(key: key); @override State<Trace> createState() => _TraceState(); } class _TraceState extends State<Trace> with TickerProviderStateMixin { late AnimationController animation; late SpringSimulation simulation; @override void initState() { super.initState(); animation = AnimationController( vsync: this, lowerBound: double.negativeInfinity, upperBound: double.infinity, // 放开角度限制,让弹簧自由运动 )..addListener(() { setState(() {}); }); // 模拟指南针从初始角度0(指向右)摆动到目标角度-pi/2(指向上,北方) simulation = SpringSimulation( const SpringDescription( mass: 0.8, // 较小质量,让指针摆动更轻盈 stiffness: 120, // 适中刚度,保证归位速度合理 damping: 12, // 适中阻尼,让摆动逐步衰减 ), 0, // 初始角度(弧度) -pi/2, // 目标角度(指向上方) 0, // 初始速度 ); animation.animateWith(simulation); } @override void dispose() { animation.dispose(); // 释放控制器,避免内存泄漏 super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: CustomPaint( painter: TracePainter(animation), child: const SizedBox.expand(), // 让CustomPaint占满整个屏幕 ), ); } } class TracePainter extends CustomPainter { TracePainter(this.animation); final Animation<double> animation; @override void paint(Canvas canvas, Size size) { double w = size.width; double h = size.height; final center = Offset(w/2, h/2); final paintcii = Paint() ..color = Colors.grey ..style = PaintingStyle.stroke ..strokeWidth = 4; final paintc = Paint()..color = Colors.grey.withOpacity(0.3); // 绘制十字参考线 canvas.drawLine(Offset(w/2, 0), Offset(w/2, h), paintc); canvas.drawLine(Offset(0, h/2), Offset(w, h/2), paintc); // 计算指针的两个端点 final end1 = center + Offset(-35 * cos(animation.value), -35 * sin(animation.value)); final end2 = center + Offset(35 * cos(animation.value), 35 * sin(animation.value)); final pathL1 = Path() ..moveTo(end1.dx, end1.dy) ..lineTo(end2.dx, end2.dy); canvas.drawPath(pathL1, paintcii); } @override bool shouldRepaint(covariant TracePainter oldDelegate) { return animation.value != oldDelegate.animation.value; } }
参数说明
- mass: 设为0.8,质量越小,指针响应越快,摆动质感越轻盈。
- stiffness: 设为120,刚度决定弹簧的"弹力",值越大,指针归位速度越快。
- damping: 设为12,阻尼决定摆动衰减速度,值过大摆动会直接停止,值过小会导致多次无意义震荡。
- 目标角度:
-pi/2对应Flutter坐标系的顶部(北方),如需指向其他方向,可调整弧度值:pi/2指向下,0指向右,pi指向左。
额外优化点
- 添加
animation.dispose(),避免内存泄漏。 - 将
child: Container()改为const SizedBox.expand(),让CustomPaint自动占满屏幕。 - 优化
shouldRepaint方法,仅在动画值变化时重绘,提升性能。
内容的提问来源于stack exchange,提问作者Ahm2025
相关产品推荐
相关产品推荐

