我想制作
CustomPaint
PathL1
的动画,就像这张图片中的指南针物理模拟一样:
但我无法达到
SpringSimulation
参数的合适值,例如mass
,stiffness
,damping
..等以及AnimationController
,如upperBound
,所以这是我的代码:
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;
}
}
这是输出结果:
使用带有
SpringDescription.withDampingRatio
参数 ratio
、< 1, check the code below where you can play with mass
和 stiffness
参数的 ratio
构造函数会更容易(默认值为 0.4
、100
和 0.9
):
class Trace extends StatefulWidget {
const Trace({super.key});
@override
State<Trace> createState() => _TraceState();
}
@override
class _TraceState extends State<Trace> with TickerProviderStateMixin {
late final animation = AnimationController.unbounded(vsync: this);
late double mass;
late double stiffness;
late double ratio;
@override
void initState() {
super.initState();
_setSpringDefaults();
}
@override
Widget build(BuildContext context) {
const N = 16;
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
Row(
children: [
const Spacer(),
Text('mass ${mass.toStringAsFixed(2)}'),
Slider(value: mass, onChanged: (v) => setState(() => mass = v), min: 0.2, max: 0.5, divisions: 30),
],
),
Row(
children: [
const Spacer(),
Text('stiffness $stiffness'),
Slider(value: stiffness, onChanged: (v) => setState(() => stiffness = v.roundToDouble()), min: 50, max: 200, divisions: 15),
],
),
Row(
children: [
const Spacer(),
Text('ratio ${ratio.toStringAsFixed(3)}'),
Slider(value: ratio, onChanged: (v) => setState(() => ratio = v), min: 0.5, max: 1.0, divisions: 20),
],
),
Row(
children: [
const Expanded(
child: Card(
color: Color(0xff88ff88),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text('to start simulation press any blue arrow'),
),
),
),
ElevatedButton(
onPressed: () => setState(_setSpringDefaults),
child: const Text('reset to defaults'),
),
],
),
const SizedBox(height: 32),
AspectRatio(
aspectRatio: 1,
child: Container(
color: Colors.black,
child: Stack(
children: [
for (int i = 0; i < N; i++)
Transform.rotate(
angle: 2 * pi * i / N,
child: Align(
alignment: Alignment.topCenter,
child: DecoratedBox(
decoration: const ShapeDecoration(
shape: CircleBorder(side: BorderSide(color: Colors.white12))
),
child: IconButton(
onPressed: () => _simulate(i / N),
icon: const Icon(Icons.north),
color: Colors.blue, iconSize: 40,
),
),
),
),
IgnorePointer(
child: CustomPaint(painter: CompassNeedlePainter(animation), child: const SizedBox.expand()),
),
],
),
),
),
],
),
),
);
}
_setSpringDefaults() {
mass = 0.4;
stiffness = 100;
ratio = 0.9;
}
@override
dispose() {
animation.dispose();
super.dispose();
}
_simulate(double end) {
final spring = SpringDescription.withDampingRatio(
mass: mass,
stiffness: stiffness,
ratio: ratio,
);
double start = animation.value;
final delta = end - start;
final increment = (delta.abs() + 0.5).floor();
if (increment != 0) {
if (delta > 0) start += increment;
if (delta < 0) end += increment;
}
assert((end - start).abs() <= 0.5);
// print('increment: $increment, start: $start. end: $end');
animation.animateWith(SpringSimulation(spring, start, end, 0));
}
}
class CompassNeedlePainter extends CustomPainter {
CompassNeedlePainter(this.animation) : super(repaint: animation);
final Animation<double> animation;
@override
void paint(Canvas canvas, Size size) {
final r = Offset.zero & size;
final paint = Paint()
..style = PaintingStyle.stroke
..color = Colors.white24
..strokeWidth = 1;
final o0 = Offset(0, -size.shortestSide * 0.25);
final o1 = Offset(0, size.shortestSide * 0.25);
final path = Path()
..moveTo(o0.dx, o0.dy)
..lineTo(o1.dx, o1.dy);
final matrix = composeMatrixFromOffsets(
translate: r.center,
rotation: animation.value * 2 * pi,
);
final rrect = RRect.fromRectAndCorners(Rect.fromCenter(center: o0, width: 16, height: 24),
topLeft: const Radius.elliptical(8, 24),
topRight: const Radius.elliptical(8, 24),
);
canvas
..drawLine(r.centerLeft, r.centerRight, paint)
..drawLine(r.topCenter, r.bottomCenter, paint)
..save()
..transform(matrix.storage)
..drawPath(path, paint..color = Colors.white..strokeWidth = 4)
..drawRRect(rrect, paint..style = PaintingStyle.fill..color = Colors.orange)
..restore();
}
@override
bool shouldRepaint(CompassNeedlePainter oldDelegate) => false;
}
Matrix4 composeMatrixFromOffsets({
double scale = 1,
double rotation = 0,
Offset translate = Offset.zero,
Offset anchor = Offset.zero,
}) {
final double c = cos(rotation) * scale;
final double s = sin(rotation) * scale;
final double dx = translate.dx - c * anchor.dx + s * anchor.dy;
final double dy = translate.dy - s * anchor.dx - c * anchor.dy;
return Matrix4(c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}