This article is published by the Cloud + community

purpose

I wrote several Flutter demos, but didn’t know much about the custom view and animation of Flutter. Seeing a similar effect implemented on Android, I tried Flutter. Also learn about Flutter’s custom view and animation.

The effect

Click in the blue area, the product water ripple animation.

Like drops of water on a pond, like drops of rain on the green grass

Train of thought

Animation is very simple, although there are multiple raindrops, but each click is a repeat animation, so how only one raindrop animation is achieved, the rest is repeated.

Looking at a raindrop animation in isolation, it’s actually a circle that gets bigger and lighter and then disappears.

So we encapsulate the above animation logic and generate an animation for each click.

implementation

The custom view

The first thing we need to solve is the problem of customising views. We know that all the UI of Flutter is Flutter, but unlike Android view, it directly provides a draw method that allows you to draw freely. In Flutter, inheritance overrides are not recommended, except for classes such as StatefuleWidget that declare support for inheritance. If you want to make a new Widget, the official advice is to do so by combining widgets.

In this case, Flutter provides the CustomPainter class. This class provides the paint method. You can override this method to draw the Canvas. You then control the display style of the Widget as an argument to CustomPaint.

Here, since the main drawing is water pattern, to achieve multiple repeated animation, so the specific drawing logic is encapsulated

class RainDrop extends CustomPainter {
  RainDrop(this.rainList);

  List<RainDropDrawer> rainList = List(); // Raindrops list
  Paint _paint = newPaint().. style = PaintingStyle.stroke;// Configure the brush

  @override
  void paint(Canvas canvas, Size size) {
    rainList.forEach((item) {
      item.drawRainDrop(canvas, _paint); // The actual drawing logic
    });
    rainList.removeWhere((item) { // Remove the invalid object
      return! item.isValid(); }); }// ...
}
Copy the code

Water circle drawing

Each water line animation is the same, so unified encapsulation.

class RainDropDrawer {
  static const double MAX_RADIUS = 30;
  double posX;
  double posY;
  double radius = 5;

  RainDropDrawer(this.posX, this.posY); / / (2)

  drawRainDrop(Canvas canvas, Paint paint) { / / (1)
    double opt = (MAX_RADIUS - radius) / MAX_RADIUS; / / (3)
    paint.color = Color.fromRGBO(0.0.0, opt);
    canvas.drawCircle(Offset(posX, posY), radius, paint); / / (4)
    radius += 0.5;
  }

  bool isValid() { / / (5)
    returnradius < MAX_RADIUS; }}Copy the code

At comment (1), the CustomPainter mentioned above will pass the canvas and complete the painting of a single water pattern here.

In note (2), the position of each water ripple should be determined. The size of each water ripple should expand evenly over time, and the default starting value should be given.

In note (3), the transparency is gradually transparent as the radius expands, and a simple linear mapping is made here.

Note (4), draw the water ripple circle, and then let the water ripple radius increase, to achieve the effect of each drawing expansion.

At note (5), the failure conditions are given. Beyond a certain radius the pattern disappears.

The spread of the animation

A number of animation implementations are provided with Flutter. The AnimationController is used here.

The AnimationController provides a callback that is updated every time a vsync signal is received.

    _animation = new AnimationController(
      Duration does not care because it is a repeat
        duration: const Duration(milliseconds: 200),
        vsync: this)
      ..addListener(() {
        if (_rainList.isEmpty) { / / (1)
          _animation.stop();
        }
        setState(() {});
      });
Copy the code

The animation here is launched by repeat, so don’t worry too much about duration, because it will actually always be called back unless you close it manually.

Vsync sets up the current widget and provides a ticker that calls back periodically. Then setState in the callback causes the current widget to update its UI.

Note (1) is the condition for animation to stop. When each click adds an object to _rainList, each object drawn will determine whether the size is valid. If not, it will be removed from the list and animation will stop when there are no elements in the list.

Gesture recognition

This basically implements the display and animation of multiple raindrops, and then we need to implement the response to the user click.

Flutter provides a GestureDetector widget for gesture recognition. So all we need to do is wrap our custom view with the widget wrap and implement the corresponding gesture listening method.

      GestureDetector(
        onTapUp: (TapUpDetails tapUp) {
          RenderBox getBox = context.findRenderObject();
          var localOffset = getBox.globalToLocal(tapUp.globalPosition); / / (1)

          var rainDrop = RainDropDrawer(localOffset.dx, localOffset.dy);
          _rainList.add(rainDrop);
          _animation.repeat(); / / (2)
        },
        child: CustomPaint(
          painter: RainDrop(_rainList),
        ),
      ),
Copy the code

This method will pass in the TapUpDetails parameter. This parameter contains the location of the lift, but it is important to note that the coordinates are for the whole screen, while the coordinates drawn are for the widget. So we need to convert this coordinate to the coordinate system in our widget. Flutter provides a tool method to do this. See the implementation in note (1).

Once the coordinate conversion is complete, you can create a “raindrop” object and add it to the List. Then start the animation in comment (2) and you can see the animation at the beginning of our article

conclusion

The animation of Flutter is really simple to implement, providing a difference callback and constantly updating. However, performance and other issues have not been considered here for the time being. I still feel very black box about setState method and don’t quite understand the specific UI refresh principle of Flutter.

I will sort out this kind of principle knowledge later, otherwise I am a little worried about whether complex animation will be stuck in this way.

This article has been published by Tencent Cloud + community authorized by the author