This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

preface

Flutter official offers such as CircularProgressIndicator and LinearProgressIndicator two common load instructions components, but to be honest, too common, Such as the following CircularProgressIndicator.

Now that we’ve introduced animation, let’s have a fun loading instruction component of our own. Where does the idea come from? Suddenly a song comes into my head:

Big windmill squeak yo yo to turn, the scenery here ah really nice! The sky is beautiful, the earth is beautiful

That’s right, it was the theme song of “Big Windmill,” a must-see program for children after school that was popular all over China.

Well, let’s create our own windmill animation to load components, the final result is as follows, support the size and rotation speed Settings.

The interface definition

Following the habit of interface first, we design the external interface first. For an animation-loaded component, we need to support two properties:

  • Size: The size of the size can be determined by the caller so that it can be used in different situations. Since the windmill load is a square, we define the parameter namedsizeThat type ofdouble.
  • Speed: The windmill is rotating and needs to support rotation speed adjustment to meet the preferences of the application user community. We define the parameter namedspeedThe unit is revolutions per second, that is, how many revolutions per second, and the typedouble.

In fact, it can also support color Settings, but look, most of the windmill is 4 leaves, the color is blue, yellow, red and green combination, here we will directly fix the color inside the component, which also simplifies the use of the caller. The next step is to define the component name, which we’ll call WindmillIndicator in English.

Implementation approach

The windmill draw

The key is to draw the windmill, according to the given size of the windmill, and then make it rotate at a set speed. The key point of drawing windmill is to draw the blades. After drawing one blade, the other three blades are rotated 90 degrees in turn. Let’s look at the drawing of the leaves. The schematic diagram of blades is as follows:The blade as a whole is in a square box of a given size and consists of three lines:

  • Red line: arc. We set the starting point at 1/3 width of bottom X-axis and the ending point at 1/3 height of left Y-axis. The radius of the arc is half of side length.
  • Green line: arc, starting from the end of the red line, the end is the top right corner, the radius of the arc is the length of the side.
  • The blue line connects the end of the green line to the beginning of the red line to achieve closure.

With blades, the rest is rotated by 90 degrees, as shown in the diagram below:

Effect of rotation

We take each blade as an independent component and change the rotation Angle according to the set speed. As long as the rotation increment Angle of the four blades is consistent at the same time, the shape of the windmill can be maintained consistently, so as to have the effect of windmill rotation.

Code implementation

WindmillIndicatordefine

WindmillIndicator requires Animation and AnimationController to control the Animation and is therefore a StatefulWidget. According to our above interface definition, WindmillIndicator is defined as follows:

class WindmillIndicator extends StatefulWidget {
  final size;
  // Rotation speed, default: 1 RPM
  final double speed;
  WindmillIndicator({Key? key, this.size = 50.0.this.speed = 1.0})
      : assert(speed > 0),
        assert(size > 0),
        super(key: key);
  
  @override
  _WindmillIndicatorState createState() => _WindmillIndicatorState();
}

Copy the code

Assert is used to prevent argument errors, such as speed not being negative or 0 (since speed will be divisible later to calculate the rotation speed), and size not being less than 0.

Rotation speed setting

We use Tween

to set the value range of the Animation. Begin and end are 0 and 1.0, and then the rotation Angle of each blade during construction is added with 2π radians multiplied by the value of the Animation object. In this way, the Animation object is rotated once in a cycle. Then the AnimationController controls the specific selection speed. The actual time is in milliseconds, 1000 / speed is the number of milliseconds required for one rotation. This allows you to set the rotation speed to speed. The code looks like this:

class _WindmillIndicatorState extends State<WindmillIndicator>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    int milliseconds = 1000 ~/ widget.speed;
    controller = AnimationController(
        duration: Duration(milliseconds: milliseconds), vsync: this);
    animation = Tween<double>(begin: 0, end: 1.0).animate(controller) .. addListener(() { setState(() {}); }); controller.repeat(); }@override
  Widget build(BuildContext context) {
    return AnimatedWindmill(
      animation: animation,
      size: widget.size,
    );
  }
  
  @override
  void dispose() {
    if(controller.status ! = AnimationStatus.completed && controller.status ! = AnimationStatus.dismissed) { controller.stop(); } controller.dispose();super.dispose();
  }
Copy the code

Controller.repeat () is called after the parameters are set in initState to make the animation repeat. In the build method, we build an AnimatedWindmill object and pass the Animation object and size to it. AnimatedWindmill is a drawing and animation component hosting class for windmills.

Windmill blade drawing

The windmill blade code is defined as follows:

class WindmillWing extends StatelessWidget {
  final double size;
  final Color color;
  final double angle;

  const WindmillWing(
      {Key? key, required this.size, required this.color, required this.angle});

  @override
  Widget build(BuildContext context) {
    return Container(
      transformAlignment: Alignment.bottomCenter,
      transform: Matrix4.translationValues(0, -size / 2.0)..rotateZ(angle),
      child: ClipPath(
        child: Container(
          width: size,
          height: size,
          alignment: Alignment.center,
          color: color,
        ),
        clipper: WindwillClipPath(),
      ),
    );
  }
}
Copy the code

Three parameters are received:

  • Size: the side length of the rectangle;
  • Color: filling color of the leaf;
  • Angle: indicates the rotation Angle of a blade.

The actual blade rotation refers to the bottomCenter position (the effect is different at different positions, you can try to modify the pull code if you are interested). There are two additional points to note:

  • transformWe did the Y firstsize / 2That’s because the whole position of the windmill is going to be down after the rotationsize / 2, so the upward compensation ensures that the windmill is located in the center.
  • The actual shape of the blade is correctContainerIt’s cropped, and it’s used hereClipPathClass.ClipPathSupports the use of customCustomClipper<Path>Clipping the boundary of the sublest element of the class. We definedWindwillClipPathClass to achieve what we call the appearance of the windmill blade clipping, that is, to cut the square into the shape of the windmill blade.WindwillClipPathThe code is as follows in the overloadedgetClipMethod will be what we call the leaf draw path can be returned.
class WindwillClipPath extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    varpath = Path() .. moveTo(size.width /3, size.height) .. arcToPoint( Offset(0, size.height * 2 / 3),
        radius: Radius.circular(size.width / 2),).. arcToPoint( Offset(size.width,0), radius: Radius.circular(size.width), ) .. lineTo(size.width /3, size.height);

    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false; }}Copy the code

The windmill components

With the windmill blade assembly, windmill assembly construction is much easier (which is one of the benefits of molecular assembly). We inherit the Windmill component from the AnimatedWidget, and then use the Stack component to combine four blades, each given a different color and rotation Angle. The rotation Angle is determined by the initial Angle of the blade and the rotation Angle controlled by the Animation object. The code for the windmill component is as follows:

class AnimatedWindmill extends AnimatedWidget {
  final size;
  AnimatedWindmill(
      {Key? key, required Animation<double> animation, this.size = 50.0})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Stack(
      alignment: Alignment.topCenter,
      children: [
        WindmillWing(
          size: size,
          color: Colors.blue,
          angle: 0 + 2 * pi * animation.value,
        ),
        WindmillWing(
          size: size,
          color: Colors.yellow,
          angle: pi / 2 + 2 * pi * animation.value,
        ),
        WindmillWing(
          size: size,
          color: Colors.green,
          angle: pi + 2 * pi * animation.value,
        ),
        WindmillWing(
          size: size,
          color: Colors.red,
          angle: -pi / 2 + 2* pi * animation.value, ), ], ); }}Copy the code

Running effect

We look at the effect of running speed of 0.5 and 1 respectively. The measured feeling is that the speed is too fast or too slow and the experience is general. The more comfortable speed is between 0.3-0.8, of course, you can dazzle the user faster 😂😂😂.

Source code has been submitted to: animation related source code, want to use in the project can directly copy the WindmillIndicator implementation source file windmill_indicator. Dart into your own project to use.

conclusion

This article has realized the loading instruction animation effect of windmill rotation. Through such effect, user experience can be improved, especially for children’s applications, which is definitely the dynamic effect of experience plus points. From the perspective of Flutter learning, there are three key points:

  • Animation,AnimationControllerAnimatedWidgetThe application of;
  • Matrix4controlContainerThe use of translation and rotation;
  • useClipPathAnd customCustomClipper<Path>Clipping component shapes is used in many scenarios, such as components with special shapes.

Finally, let’s take a look at your childhood memories. When you see this windmill animation, do you remember the show “Big Windmill” and the upbeat theme song? See “big Windmill” what is the most impressive scene? Feel free to share your childhood memories in the comments!

I am dao Code Farmer with the same name as my wechat official account. This is a column about the introduction and practice of Flutter, providing systematic learning articles about Flutter. See the corresponding source code here: The source code of Flutter Introduction and Practical column. If you have any questions, please add me to the wechat account: island-coder.

👍🏻 : feel the harvest please point a praise to encourage!

🌟 : Collect articles, easy to look back!

💬 : Comment exchange, mutual progress!