See all the source code in this articleGithub.com/MoonRiser/F…

If you want to ask about the hottest mobile framework of 2019, it has to be Google’s Flutter.


rendering

(Here for the convenience of recording GIF, animation set faster; If the Duration of the animation is set to 20s, it will look like a floating effect.) The effect of particle collision is a reference to the particles of The Flutter animation by Zhang Fengjitrel

1. Animation principle of Flutter

In the UI framework of any system, the principle of animation is the same: changing the appearance of the UI many times quickly over a period of time; Because the human eye produces a visual pause, what it ends up seeing is a “continuous” animation, just like a movie. We call each UI change an animation Frame for each screen refresh, and one of the most important indicators of animation smoothness is Frame Per Second, or the number of animation frames Per Second.

In short, it’s frame by frame, and as long as the screen refreshes fast enough, it feels like a continuous animation. Imagine an animation of a ball moving from the top of the screen to the bottom. What data do we need in order to do this animation?

  • The trajectory of the ball, that is, the starting point S, the ending point E, and any point P in between
  • Animation duration t

Are these two parameters enough? Obviously not enough, because the ball follows a given trajectory, which may be uniform, fast then slow, slow then fast, or even alternating between fast and slow, as long as it is done within time T. So we should specify another parameter c to control the speed of the animation.

1.1 vsync inquiry

Without further ado, let’s look at the code for the animation part of Flutter:

AnimationController controller = AnimationController( vsync: this, duration: Duration(seconds: 2), ).. addListener(() { //_renderBezier();print(controllerG.value);
        print('This is the callback ${++count});
      });
Copy the code

Briefly, the AnimationController, as the name suggests, controls the playback of the animation. Duration is the animation duration (t); vsync is the animation duration (t). For those of you who have played video games, vsync means vertical synchronization. So what is vSYNC?

Vertical synchronization is also called Vertical Hold. From the display principle of CRT display, a single pixel forms a horizontal scan line, and the horizontal scan line is stacked in the Vertical direction to form a complete picture. The refresh rate of the display is controlled by the graphics DAC, which generates a vSYNC signal after completing a frame of scanning. What we usually mean by turning on vSYNC is that the signal is sent into the 3D graphics processing part of the graphics card, so that the graphics card is constrained by the VSYNC signal when generating 3D graphics.

In short, after rendering, the graphics card sends the picture data to video memory, and the display pulls the picture data from video memory, line by line, from top to bottom, for display. But the screen refresh rate data and graphics rendering speed is a lot of the time don’t match, just imagine, just scan images of the upper part of the screen display, is preparing to draw on the memory of the below image data, graphics card comes the next frame image data covering the original memory, the following part of the time display of images and the above do not match, Causing the image to tear.

In order to avoid this situation, we introduced vSYNC signal, only after the display complete scan display a frame, the video card received vSYNC signal can refresh the video memory. But what does this physical signal have to do with our Flutter animation? The parameter vsync corresponds to is this, so let’s look at the following classes that this corresponds to.

class _RunBallState extends State<RunBall> with TickerProviderStateMixin 
Copy the code

The with keyword is a method that uses the class rather than inheriting it. Mixins are Java-like interfaces, except that the methods in mixins are not abstract methods but implemented methods.

What exactly does TickerProviderStateMixin do? With the help of buddy Google, I found it on the Internet

As for the animation driver, briefly, the Ticker is driven by SchedulerBinding. The SchedulerBinding listens for the window. onBeginFrame callback. The function of the Window. OnBeginFrame is to tell the application to provide a scene, which is driven by the hardware’s VSync signal.

We finally found that, after all, the actual hardware generated the vSYNC signal driving the Flutter animation.

..addListener(() {
        //_renderBezier();
        print(controllerG.value);
        print('This is the callback ${++count});
      });

Copy the code

Note that there is an animation controller listener in the previous code. During the animation’s execution time, the function callback controller.value generates a value of type 0 to 1 double. We printed the following result on the console:

After observation and two tests, the callback function was executed 50 times and 53 times respectively in 2s animation execution time, which was not a fixed value. This means that the screen refresh rate of the hardware (emulator) is around (25 ~ 26.5 frames /s).

Conclusion: Hardware determines the animation refresh rate

1.2 Animate

Now that we understand how animation works, it’s time to draw frame by frame. The custom View for Flutter is similar to the Android native View.

Inherit the CustomPainter class and override paint and shouldRepaint.

class Ball { double aX; double aY; double vX; double vY; double x; double y; double r; Color color; }Copy the code

The Ball has center coordinates, radius, color, speed, acceleration and other properties. The effect of uniform acceleration can be realized by calculating the relationship between speed and acceleration through mathematical expressions.

// Kinematic formula, looks like missing time t; These functions actually call back frame-by-frame during animation, counting the refresh interval per frame as a unit of time, equivalent to t=1 _ball.x += _ball.vX; // Displacement = velocity * time _ball. Y += _ball. _ball.vX += _ball.aX; // Speed = acceleration * time _ball. VY += _ball.Copy the code

The controller causes the function to call back repeatedly, changing the parameters of the ball in the callback function, and calling setState () to redraw the UI; The ball’s trajectory coordinates constantly change, so that the ball appears to be moving frame by frame. You can even add effects that make the ball change color or have a smaller radius when it hits the boundary (see the particle collision renderings at the beginning of this article).

2. Thinking about the random floating of the ball

The question is, do I want a floating effect? It’s better to have a random trajectory, like a bubble floating in the air, and that got me thinking; Constant velocity and then random direction? The feeling is not elegant enough, searched on the net then, discovered train of thought!

Firstly, a Random Bezier curve is generated as a trajectory, and when the ball moves to the end, a new bezier curve trajectory is generated

The formula for generating second-order Bezier curves is as follows:

// Quadratic Bezier curve trajectory coordinates, according to the parameter t return coordinates; Starting point P0, control point P1, Offset quadBezierTrack(double t, Offset P0, Offset p1, Offset p2) { var bx = (1 - t) * (1 - t) * p0.dx + 2 * t * (1 - t) * p1.dx + t * t * p2.dx; var by = (1 - t) * (1 - t) * p0.dy + 2 * t * (1 - t) * p1.dy + t * t * p2.dy;return Offset(bx, by);
}
Copy the code

Coincidentally, we need to pass in a double t between 0 and 1. As we mentioned earlier, the animationController will generate a value between 0 and 1 at any given time; What a coincidence.

Needless to say, the starting point, the next thing we have to do is solve for control points P1 and P2, randomly generating those two, of course, but what if we have multiple balls at the same time? For example, five balls float at the same time, and each Ball corresponds to a group of three coordinate information. Add three coordinate attributes to the Ball Ball? No, at this point, we can make clever use of random numbers with seed parameters.

We know that when generating random numbers, if the seeds are the same, the random numbers will be the same every time.

Each ball object is created with an integer ID that acts as a random seed. For example, five balls, we start with ids: 2,4,6,8,10;

Offset p0 = ball.p0; // Offset p1 = _randPosition(ball-.id); Offset p2 = _randPosition(ball.id + 1);Copy the code

Rand (2) and RAND (2+1) are the coordinates p1 and P2 of the first ball; When all the balls reach the end point, p2, the original end point, is the starting point of the new Bezier curve. At this time, the corresponding id should also be increased. In order to prevent repetition, the number of balls in id should be increased by 5 *2, that is, at the beginning of the second round of movement, the id of 5 balls is: 12,14,16,18,20. So this ensures that for each round of bezier’s curve, for each ball, P0, P1, p2 are fixed; The new round of motion requires three random coordinate points, just change the value of id.

Path quadBezierPath(Offset p0, Offset p1, Offset p2) {
  Path path = new Path();
  path.moveTo(p0.dx, p0.dy);
  path.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);
  return path;
}
Copy the code

At this time, we can also use the Flutter API to draw the quadratic Bezier curve trajectory to see if the ball’s motion falls on the trajectory.

2.1 Some Details

animation = CurvedAnimation(parent: controllerG, curve: Curves.bounceOut);
Copy the code

The “Curve” that controls the animation process is the parameter mentioned earlier. The flutter has many effects. My favorite one is the bounceOut effect.

 animation.addStatusListener((status) {
      switch (status) {
        case AnimationStatus.dismissed:
          // TODO: Handle this case.
          break;
        case AnimationStatus.forward:
          // TODO: Handle this case.
          break;
        case AnimationStatus.reverse:
          // TODO: Handle this case.
          break;
        case AnimationStatus.completed:
          // TODO: Handle this case.
          controllerG.reset();
          controllerG.forward();
          break; }});Copy the code

To monitor the state of the animation process, when the round at the end of the animation, the status status to AnimationStatus.com pleted; At this point, we reset the controller, and then forward restart, this time will start a new round of animation effect; If we choose reverse, the animation will play backwards.


GestureDetector( child: Container( width: double.infinity, height: 200, child: CustomPaint( painter: FloatBallView(_ballsF, _areaF), ), ), onTap: () { controllerG.forward(); }, onDoubleTap: () { controllerG.stop(); },),Copy the code

In order to facilitate control, I also added a gesture listener, click to control the animation running, double click to pause the animation.

3 the end

The level is limited, if there are mistakes in the article, please point out that I am a Dragon