flutter 中的物理模拟和动画

问题描述 投票:0回答:1

我想制作

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;
  }
}

这是输出结果:

flutter dart flutter-animation
1个回答
0
投票

使用带有

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);
}
© www.soinside.com 2019 - 2024. All rights reserved.