[Fluth]状態が変化し続ける可変アニメーション


私は最近Flutterのアニメーションを理解して開発しています.アニメーションは非常に重要な要素であり、Fluter 2.0からWEBまでをサポートし、Reactなどの既存のWebフレームワークと競争力を維持していると思います.

Basic Animations in Flutter


flutterが提供する基本アニメーションコンポーネントを使用して、簡単なアニメーションを実現することができます.例えばScale Transition、Fade Transitionなどの部品.さらに、より豊富なアニメーションを作成するために、Animated Builder、Tween Animation Builderなどのコンポーネントを使用することもできます.しかし、私は最近、これらの部品のアニメーションを通じていつも同じアニメーションしか表示されないことを開発し、体験しました.△これが正しい言い方かどうか分かりません.この文章を読むと、私の言うことがわかりますよ.だから私はFlutterでもっと特別なアニメーションを実現したいと思っています.

Reflecting Ball Animation


   ReflectBall(
          ballRad: 20,
          mapXsize: 300,
          mapYsize: 500,
          xPosition: 100,
          yPosition: 200,
          xSpeed: 4,
          ySpeed: 4,
        );
これが実現したいアニメーション部品ReflectBallです.緑の箱の中にボールが動いていて、壁にぶつかると反射します.これがどのように実現されたのか説明します.

Factors


上のReflectBallの部品を見ると、入力値としていくつかのパラメータがあります.ボールの半径、ボールが移動する箱の大きさ、ボールの初期位置、ボールの速度です.これらのパラメータは必要だと思いますので、requiredオプションを追加しました.
class ReflectBall extends StatefulWidget {
  final double mapXsize;
  final double mapYsize;
  final double xPosition;
  final double yPosition;
  final int ballRad;
  final double xVector;
  final double yVector;
  final double xSpeed;
  final double ySpeed;

  const ReflectBall({Key? key,
    required this.mapXsize,
    required this.mapYsize,
    required this.xPosition,
    required this.yPosition,
    required this.ballRad,
    this.xVector = 1,
    this.yVector = 1,
    required this.xSpeed,
    required this.ySpeed
  }) : super(key: key);

  
  _ReflectBallState createState() => _ReflectBallState();
}

Infinite Animation


前述したように、この球の反射アニメーションは無限に続きます.Fluthでアニメーションを再生し続けるには、以下の簡単な方法があります.

  • Set Duration very long
    AnimationController(
           vsync: this,
           duration: Duration(seconds: 1000)
       );
    正確には、無限ではありませんが、一般的には1つの画面で何時間も見ることはありません.

  • Repeat() Method
    _animationController.repeat();
    コードに従って繰り返す.

  • addStatusListener
    _animationController.forward();
    _animationController.addStatusListener((status) {
         if (status == AnimationStatus.completed) {
           _animationController.reverse();
         } else if (status == AnimationStatus.dismissed) {
           _animationController.forward();
         }
       });
    これは、Fluterアニメーションのアニメーション状態値を制御する方法です.AからBまでのアニメーションがある場合、上のコードはAからBで、BからAまでのアニメーションが繰り返されます.
  • Flutterのアニメーションを使用したことがある人は、上記の3つの方法を使用すると、アニメーションは無限に実行されますが、アニメーションは常に同じアニメーションです.しかし、これは私が望んでいるものではありません.反射球のアニメーションを作成するには、パラメータを常に変化させなければなりません.例えば、ボールは移動しながら位置を変化させ続け、位置の変化に伴って壁面衝突を満たす条件下でも方向を変化させる.もちろん!測定可能な経路であるため、予め球の移動経路を設定してから直接()を繰り返すことができる.でも価値がない
    このように構成されたアニメーションの変数が変化するアニメーションを可変アニメーションと呼ぶことにしました!

    Infinite Variable Animation


    上記の3つ目の方法では、AnimationStatusとAnimationの関係は何ですか?AndroidスタジオでF 4を押してからアニメーションを行います.dartファイルを表示すると、次のコメントが表示されます.
      // The result of this function includes an icon describing the status of this
      // [Animation] object:
      //
      // * "▶": [AnimationStatus.forward] ([value] increasing)
      // * "◀": [AnimationStatus.reverse] ([value] decreasing)
      // * "⏭": [AnimationStatus.completed] ([value] == 1.0)
      // * "⏮": [AnimationStatus.dismissed] ([value] == 0.0)
    この注釈により、Animationの値がAnimationStatusを決定します!だから私は次のコードを書きました.
        _animationController.forward();
        _animationController.addStatusListener((status) {
          if(status == AnimationStatus.completed ){
            _animationController.value=0;
            _animationController.forward();
          }
        });
    _animationController.value値は、アニメーションの進行に伴って0から1に連続して増加します.コードの最初の行.forward()メソッドでアニメーションを1回実行し、0の値を1に設定します.AnimationStatusは完了し、addStatusListerはifクエリー条件を確立します.
    次にif文でvalue値を0に設定すると、再び.forward()メソッドを実行できます!まずはここまでrepeat()と同じ機能を持ちます.
        _animationController.forward();
        _animationController.addStatusListener((status) {
          if(status == AnimationStatus.completed ){
            setState(() {
              if(xPos >= (widget.mapXsize - widget.ballRad) || xPos <= widget.ballRad){
                xVec*=-1;
              }
              if(yPos >= (widget.mapYsize - widget.ballRad) || yPos <= widget.ballRad){
                yVec*=-1;
              }
    
              xPos+=widget.xSpeed*xVec;
              yPos+=widget.ySpeed*yVec;
    
            });
            _animationController.value=0;
            _animationController.forward();
          }
        });
    でもsetState文を追加できるようになりました!!上のコードではsetStateゲート内のコードがボールの位置が所定のマップサイズからずれると、ボールのベクトル方向が反転し、x軸とy軸でボールの位置を速度X方向に加算します.(2 Dベクトル分解)
    ここで重要なのは、setState文を使用してアニメーションの情報を変更し続けることです.

    Build Widgets

      
      Widget build(BuildContext context) {
        return AnimatedBuilder(
          animation: _animationController,
          builder: (context, child) {
            return Container(
              width: widget.mapXsize,
              height: widget.mapYsize,
              color: Colors.lightGreen,
              child: CustomPaint(
                painter: _ball(
                  animationValue: _animationController.value,
                  xVector: xVec,
                  yVector: yVec,
                  xPosition: xPos,
                  yPosition: yPos,
                  ballRad: widget.ballRad,
                  xSpeed: widget.xSpeed,
                  ySpeed: widget.ySpeed
                ),
              ),
            );
          },
        );
      }
    class _ball extends CustomPainter {
      final animationValue;
      final xPosition;
      final yPosition;
      final xVector;
      final yVector;
      final ballRad;
      final xSpeed;
      final ySpeed;
    
      _ball({
        required this.animationValue,
        required this.xPosition,
        required this.yPosition,
        required this.xVector,
        required this.yVector,
        required this.ballRad,
        required this.xSpeed,
        required this.ySpeed,
    
      });
    
      
      void paint(Canvas canvas, Size size) {
    
        Paint paint = Paint()
          ..color = Colors.indigoAccent
          ..style = PaintingStyle.stroke
          ..strokeWidth = 2;
    
        Path path = Path();
    
        for(double i=0; i<ballRad; i++){
          path.addOval(Rect.fromCircle(
            center: Offset(
              xPosition + animationValue*xSpeed*xVector,
              yPosition + animationValue*ySpeed*yVector,
            ),
            radius: i
          ));
        }
        path.close();
    
        canvas.drawPath(path, paint);
      }
    
      
      bool shouldRepaint(CustomPainter oldDelegate) => true;
    }
    Animated BuilderとCustom Paintを使いました.Custom Paint Classで、pathのaddoval文とfor文で円を描きます.x軸位置、y軸位置などの値をパラメータに戻し直し、球を他の位置に描画し続けることができます.

    Conclusion


    私が望むアニメーションを実現するために100%完璧なコードであることは保証できません.アニメーションの描画中の最適化などの面で改善が必要です.しかし、私はそれを実現すると同時に、自分の力でグーグルにも見つからない答えを探して、ある程度この問題を解決したことは、非常に大きな意味を持っています.本当に嬉しかった
    もし私の文章を見たら、私のコードに問題があるか、改善する必要があるところがあれば、必ず!広告を貼ってほしいです.

    Full Code

    import 'package:flutter/material.dart';
    
    class ReflectBall extends StatefulWidget {
      final double mapXsize;
      final double mapYsize;
      final double xPosition;
      final double yPosition;
      final int ballRad;
      final double xVector;
      final double yVector;
      final double xSpeed;
      final double ySpeed;
    
      const ReflectBall({Key? key,
        required this.mapXsize,
        required this.mapYsize,
        required this.xPosition,
        required this.yPosition,
        required this.ballRad,
        this.xVector = 1,
        this.yVector = 1,
        required this.xSpeed,
        required this.ySpeed
      }) : super(key: key);
    
      
      _ReflectBallState createState() => _ReflectBallState();
    }
    
    class _ReflectBallState extends State<ReflectBall> with SingleTickerProviderStateMixin{
      late AnimationController _animationController;
      late double xPos;
      late double yPos;
      late double xVec;
      late double yVec;
    
      
      void initState() {
        // TODO: implement initState
        super.initState();
    
        xPos = widget.xPosition;
        yPos = widget.yPosition;
        xVec = widget.xVector;
        yVec = widget.yVector;
    
        _animationController = AnimationController(
            vsync: this,
            duration: Duration(milliseconds: 1)
        );
        _animationController.forward();
    
        _animationController.addStatusListener((status) {
          if(status == AnimationStatus.completed ){
            setState(() {
              if(xPos >= (widget.mapXsize - widget.ballRad) || xPos <= widget.ballRad){
                xVec*=-1;
              }
              if(yPos >= (widget.mapYsize - widget.ballRad) || yPos <= widget.ballRad){
                yVec*=-1;
              }
    
              xPos+=widget.xSpeed*xVec;
              yPos+=widget.ySpeed*yVec;
    
            });
            _animationController.value=0;
            _animationController.forward();
          }
        });
      }
    
      
      void dispose(){
        _animationController.dispose();
        super.dispose();
      }
    
      
      Widget build(BuildContext context) {
        return AnimatedBuilder(
          animation: _animationController,
          builder: (context, child) {
            return Container(
              width: widget.mapXsize,
              height: widget.mapYsize,
              color: Colors.lightGreen,
              child: CustomPaint(
                painter: _ball(
                  animationValue: _animationController.value,
                  xVector: xVec,
                  yVector: yVec,
                  xPosition: xPos,
                  yPosition: yPos,
                  ballRad: widget.ballRad,
                  xSpeed: widget.xSpeed,
                  ySpeed: widget.ySpeed
                ),
              ),
            );
          },
        );
      }
    }
    
    class _ball extends CustomPainter {
      final animationValue;
      final xPosition;
      final yPosition;
      final xVector;
      final yVector;
      final ballRad;
      final xSpeed;
      final ySpeed;
    
      _ball({
        required this.animationValue,
        required this.xPosition,
        required this.yPosition,
        required this.xVector,
        required this.yVector,
        required this.ballRad,
        required this.xSpeed,
        required this.ySpeed,
    
      });
    
      
      void paint(Canvas canvas, Size size) {
    
        Paint paint = Paint()
          ..color = Colors.indigoAccent
          ..style = PaintingStyle.stroke
          ..strokeWidth = 2;
    
        Path path = Path();
    
        for(double i=0; i<ballRad; i++){
          path.addOval(Rect.fromCircle(
            center: Offset(
              xPosition + animationValue*xSpeed*xVector,
              yPosition + animationValue*ySpeed*yVector,
            ),
            radius: i
          ));
        }
        path.close();
    
        canvas.drawPath(path, paint);
      }
    
      
      bool shouldRepaint(CustomPainter oldDelegate) => true;
    }