Flutter trigonometry animation elements not reaching their desired positions at the same time

126

I think your problem is how you compute your endingX.

var endingX = middleX + cos(radians) * radius;

It seems that your endingX should be the distance between the side of the Canvas and the perimeter of the Circles in their initial position. It's, therefore, the same for both directions:

var endingX = middleX - radius;

Then, a few simplifications on your code:

negativeOrPositiveOrZero

You have a getter for that in dart:math: sign

Trigonometry

I suppose the sample you posted is much simpler than your real code and the simplifications hereafter are probably not meaningful.

However, pay attention that near zero calculus on computers is quite messy!

import 'dart:math';

void main() {
  print('   sin(0).sign: ${sin(0).sign}');
  print('   sin(0).sign: ${sin(0).sign}');
  print('------------------------------------------');
  print('   sin(180*pi/180): ${sin(180*pi/180)}');
  print('!! sin(180*pi/180).sign: ${sin(180*pi/180).sign}');
}
   sin(0).sign: 0
   sin(0).sign: 0
------------------------------------------
   sin(180*pi/180): 1.2246467991473532e-16
!! sin(180*pi/180).sign: 1

Full example after correction and simplification

enter image description here

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Rolling balls',
      home: Scaffold(
        body: Center(
          child: Container(
            width: 300,
            height: 200,
            color: Colors.amber.shade300,
            child: const MyCustomPaint(),
          ),
        ),
      ),
    );
  }
}

class MyCustomPaint extends StatefulWidget {
  const MyCustomPaint({Key? key}) : super(key: key);

  @override
  _MyCustomPaintState createState() => _MyCustomPaintState();
}

class _MyCustomPaintState extends State<MyCustomPaint>
    with SingleTickerProviderStateMixin {
  late Animation<double> _animation;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 4),
    );

    Tween<double> _animationTween = Tween(begin: 0.0, end: 1.0);

    _animation = _animationTween.animate(_controller)
      ..addListener(() => setState(() {}))
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      });
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: MyCustomPainter(_animation.value),
    );
  }
}

class MyCustomPainter extends CustomPainter {
  final double percentFinished;

  MyCustomPainter(this.percentFinished);

  @override
  void paint(Canvas canvas, Size size) {
    double middleY = size.height / 2;
    double middleX = size.width / 2;
    double radius = size.width / 20;

    Paint paint = Paint()..color = Colors.black;

    for (int direction in [1, -1]) {
      var endingX = middleX - radius;
      var addToX = direction * percentFinished;
      canvas.drawCircle(
        Offset(endingX * addToX + middleX, middleY),
        radius,
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}
Share:
126
Lightning Gaming
Author by

Lightning Gaming

Updated on January 03, 2023

Comments

  • Lightning Gaming
    Lightning Gaming over 1 year

    The animation begins like this, with both circles in the center, but ends up where one circle reaches the end faster for some reason. How could I make it so that they reach the ends at the same time? I don't understand why this code isn't working. Any help would be much appreciated, thank you!

      @override
      void paint(Canvas canvas, Size size) {
        var percentFinished = _animation.value;
        var middleY = size.height / 2;
        var middleX = size.width / 2;
        double radius = 40;
    
        var angles = [180, 0];
    
        for (var angle in angles) {
          var radians = (angle * pi) / 180;
          var endingX = middleX + cos(radians) * radius;
          var endingY = middleY - sin(radians) * radius;
          var addToY = negativeOrPositiveOrZero(sin(radians)) * percentFinished;
          var addToX = negativeOrPositiveOrZero(cos(radians)) * percentFinished;
    
          canvas.drawCircle(Offset(endingX * addToX + middleX, endingY * addToY + middleY), 10, Paint());
        }
      }
    
      int negativeOrPositiveOrZero(double a) {
        int num = a.toInt();
        if (num > 0){
          print("1" + ": " + num.toString());
          return 1;
        }
        else if (num < 0) {
          return -1;
        }
        else {
          return 0;
        }
      }
    
    

    Below is just some screenshots of what I'm talking about.

    The animation starts like this, with two balls in the center

    animation start

    But it ends in this state where one circle reaches the end before the other. The desired behavior is to have them reach their side of the screen at the same time.

    end of animation