How to use fluent to realize loading animation in 58 cities

Time:2020-7-20

preface

When executing time-consuming operation in the application, in order to avoid the phenomenon that the interface waiting for a long time to cause feign death, a loading animation is often added to remind the user, which is no exception in 58 cities. Moreover, we did not use the default loading animation of the system, but made a loading animation with 58 characteristics.

In this article, I would like to share with you the process of loading animation in 58 cities by using flutter. Let’s take a look at the effect of loading animation

At first glance, the animation effect is more complex, it is difficult to see the clue. In fact, we can slow down the animation speed first, so that we can analyze the animation process clearly.

Animation process

The animation is composed of the dynamic effects of two arcs. The starting point angle and swept radian of the two arcs change with time. Careful observation will find that the dynamic effect of the two arcs is actually the same, but the starting position is not the same. Let’s first look at the law of motion of the large outer arc.

According to the motion law of animation, the animation can be divided into three stages

The first stage: the starting point of the arc is in the positive direction of the x-axis, and the angle of the end point starts to increase gradually from the positive direction of the x-axis until the end point reaches the position in the negative direction of the y-axis. Finally, the angle swept by the arc is 180 degrees.

The second stage: the swept angle of the arc is kept at 180 degrees. The starting point and the end point rotate clockwise together until the end point reaches the positive direction of X axis after 180 degrees of rotation.

The third stage: the end point of the arc is kept in the positive direction of the x-axis, and the starting point rotates clockwise until the starting point also reaches the positive direction of the x-axis. Next, continue to repeat the first stage of the animation to form a coherent animation.

After analyzing the animation process, the idea is very clear. According to the animation process, we divide the animation into three parts. Through the transformation of the starting point, end point and sweeping angle of the arc, we combine it into a complete animation, and then repeat it continuously, and finally it becomes an animation effect in loading.

Next, start writing code to implement it.

Because the animation is composed of an arc constantly changing, if we use Android, we naturally think that we can use canvas to draw the arc, and then re draw the arc according to the change of time, so as to achieve the animation effect. So is there a canvas in flutter? The answer is yes. Flutter, like Android, also has a canvas.

Canvas in fluent

Flutter uses the custompainter class to draw on canvas. This class contains a paint () method, which provides a canvas object, which can be used to draw various graphics.


 abstract class CustomPainter extends Listenable {

 void paint(Canvas canvas, Size size);

 }

In flutter, however, everything is a widget, and the widget that carries the canvas function is the custompaint class. Custompaint contains a painter attribute to specify the custompainter to paint. The source code is as follows:


 class CustomPaint extends SingleChildRenderObjectWidget {

 const CustomPaint({

 Key key,

 this.painter,

 });

 final CustomPainter painter;

 }

The canvas in flutter is similar to Android. It provides a series of APIs to draw points, lines, circles, squares, etc., and the API is very similar. Compare the common APIs of canvas in flutter and Android (please refer to the document and source code for the specific parameter list. The space is limited and will not be listed one by one)

Android Flutter
spot

drawPoint()

drawPoints()

drawPoints()
Line

drawLine()

drawLines()

drawLine()
circular drawCircle() drawCircle()
ellipse drawOval() drawOval()
arc drawArc() drawArc()
rectangle drawRect() drawRect()
Path drawPath() drawPath()
picture drawBitmap() drawImage()
written words drawText() drawParagraph()
Transformation

save()

restore()

save()

restore()

 

In order to draw the arc in animation, we should use the drawarc() method to achieve it. Here, we need to pay attention to the parameters of the drawarc() method: the units of startangle and sweetangle are radians (180 degrees equals π radians).

Take a look at it in detail Canvas.drawArc Parameter list of () method:

///Rect: the rectangle formed by the area around the arc. In this article, the arc is a circle, which can be used Rect.fromCircle () determine the range of the arc

 ///Startangle: the angle of the starting point of the arc. The positive direction of the X axis is 0 degrees, increasing clockwise, and the negative direction of the Y axis is 90 degrees, and so on

 ///Sweetangle: the angle swept by the arc, that is, the angle of the end point of the arc is startangle + sweetangle

 ///Usecenter: if it is true, the two ends of the arc will be connected with the center of the circle to form a fan, which should be false in this article

 ///Paint: brush, which will be briefly introduced in the following

 void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)

The name of a familiar painting brush: paint is similar to the name of a painting brush in Android.

Paint class

The paint class is located in the dart.ui In the library, the paint class saves the color, thickness, anti aliasing, shaders and other attributes of the brush.

The following is a brief introduction to the following common attributes:


 Paint paint = Paint()

 ..color = Color(0xFFFF552E)

 ..strokeWidth = 2.0

 ..style = PaintingStyle.stroke

 ..isAntiAlias = true

 ..shader = LinearGradient(colors: []).createShader(rect)

 ..strokeCap = StrokeCap.round

 ..strokeJoin = StrokeJoin.bevel;

Attribute description:

  • Color: color type to set the color of the brush.
  • Strokewidth: double type, which sets the thickness of the brush.
  • Style: paintingstyle enumeration type, set brush style, PaintingStyle.stroke For stroke, PaintingStyle.fill For filling.
  • Isantialias: bool type. Set whether anti aliasing is enabled. True means to enable anti aliasing.
  • Shader: shader type, shader, generally used to draw gradient effects. Linear gradient, radial gradient, sweetgradient, etc. can be used.
  • Strokecap: the strokecap enumeration type, which sets the style of the two ends of a line, StrokeCap.butt Is none (default value), StrokeCap.round It is round, StrokeCap.square It’s Square.
  • Strokejoin: the strokejoin enumeration type, which sets the style of line intersection, StrokeJoin.miter It is an acute angle, StrokeJoin.round Arc, StrokeJoin.bevel For oblique angle, please refer to the following figure for easy understanding:

After being familiar with the use of canvas and paint, you can draw the arc loaded with animation. Of course, it’s useless just to draw an arc. It’s mainly about how to make the arc move.

Animation in fluent

To make the arc move, we need to use the animation of the fluent. Next, let's introduce the implementation of animation in fluent.

Animation in fluent相关的类主要有以下几个:

 Animation: the core class of animation, which is an abstract class. It is used to generate interpolation during animation execution. The output can be linear or curvilinear. The animation object has nothing to do with UI rendering.

 abstract class Animation<T> extends Listenable implements ValueListenable<T> {

  ///Add monitoring of animation status

  void addStatusListener(AnimationStatusListener listener);


  ///Remove monitoring of animation state

  void removeStatusListener(AnimationStatusListener listener);


  ///Gets the status of the current animation

  AnimationStatus get status;


  ///Get the interpolation of the current animation. When executing the animation, you need to draw the UI according to the value

  T get value;

 }

Animationcontroller: animation management class, inherited from animation < double >. By default, values from 0.0 to 1.0 are generated linearly over a given time range.

The animationcontroller object needs to pass a Vsync parameter. It receives an object of type tickerprovider. Its main responsibility is to create ticker. When the flutter application starts, it will bind a schedulerbinding to add a callback to each screen refresh. Ticker is to add a callback of screen refresh through schedulerbinding. When the screen refreshes, it will notify the bound ticker callback. If the UI of the animation is not on the current screen, for example, when the screen is locked, the screen will stop refreshing after the screen is locked. The scheduler binding will not be notified, and the ticker will not be triggered. In this way, the animation outside the screen can be prevented from consuming unnecessary resources.

class AnimationController extends Animation<double>

  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {

  ///Value: the initial value of the animation. The default value is Lowerbound

  ///Duration: the duration of animation execution

  ///Lowerbound: the minimum value of the animation. The default value is 0.0

  ///Upperbound: the maximum value of the animation. The default value is 1.0

  ///Vsync: statefulwidget objects can be passed in through 'with singletickerproviderstatemixin'

  AnimationController({

  double value,

  this.duration,

  this.lowerBound = 0.0,

  this.upperBound = 1.0,

  @required TickerProvider vsync,

  }) {

  _ticker = vsync.createTicker(_tick);

  }


  Ticker _ticker;


  ///Ticker's callback, which will be called back every time the screen is refreshed

  void _tick(Duration elapsed) {

  notifyListeners();

  }


  ///Start playing the animation

  TickerFuture forward({ double from })


  ///Reverse animation

  TickerFuture reverse({ double from })


  ///Set animation repeat

  TickerFuture repeat({ double min, double max, bool reverse = false, Duration period })


  ///Release animation resources

  void dispose()

 }

Curvedanimation: nonlinear animation class, inherited from animation < double >. Curvedanimation can use the curve attribute to specify the curve function curve. Similar to the interpolator of Android animation, many commonly used curves have been implemented in flutter, which can be found in the curves class, such as Curves.linear , Curves.decelerate , Curves.ease 。 You can also inherit the curve class and override the transform () method to implement custom curve functions.

class CurvedAnimation extends Animation<double>

  with AnimationWithParentMixin<double> {

  ///Parent: Specifies the animationcontroller object

  ///Curve: Specifies the curve function of the animation

  CurvedAnimation({

  @required this.parent,

  @required this.curve,

  })

 }


 abstract class Curve {

  ///The interpolation of't 'points in animation execution can be calculated, and the curve function can be customized

  double transform(double t)

 }

Tween: generation class of complement value, inherited from animatable < T >.

Since the value range of animationcontroller is 0.0 to 1.0 by default, you can use tween to specify the range of animation values if you need a different range or data type. Tween can not only return values of double type, but also various subclasses that return different data types, such as inttween, colortween, sizeitween.
To use tween object, you need to call the animate() method and pass in the animationcontroller object. This method will return an animation so that the interpolation of the animation can be obtained.

class Tween<T extends dynamic> extends Animatable<T> {

  ///Begin: the starting value of the animation

  ///End: the end value of the animation

  Tween({ this.begin, this.end });


  ///You can convert double type animation interpolation to any type of value

  T transform(double t)


  ///Parent: pass in the animationcontroller object

  ///Return the animation object and use the Animation.value Gets the current interpolation of the animation

  Animation<T> animate(Animation<double> parent)

 }

Animatedbuilder: a widget used to build animation. It associates the animation with the widget to be executed. The inheritance relationship is animatedbuilder → animatedwidget → stateful widget.

class AnimatedBuilder extends AnimatedWidget {

  const AnimatedBuilder({

  @required Listenable animation,

  @required this.builder,

  });


  /// typedef TransitionBuilder = Widget Function(BuildContext context, Widget child);

  ///Builder is a function that returns the widget object

  final TransitionBuilder builder;


  @override

  Widget build(BuildContext context) {

  return builder(context, child);

  }

 }


 abstract class AnimatedWidget extends StatefulWidget {

  const AnimatedWidget({

  @required this.listenable,

  });


  @protected

  Widget build(BuildContext context);


  @override

  _AnimatedState createState() => _AnimatedState();

 }


 class _AnimatedState extends State<AnimatedWidget> {

  @override

  void initState() {

  super.initState();

  widget.listenable.addListener(_handleChange);

  }


  @override

  void dispose() {

  widget.listenable.removeListener(_handleChange);

  super.dispose();

  }


  void _handleChange() {

  setState(() { });

  }


  @override

  Widget build(BuildContext context) => widget.build(context);

 }

Analyzing the source code listed above, animatedwidget is a stateful widget. When the animatedwidget is associated with_ When animatedstate is initialized, the listener function of the animation is registered_ handleChange,_ In the handlechange listening function, the setstate() method is called, that is, the build() method is called every time the animation interpolation is changed_ AnimatedState.build () method AnimatedWidget.build () method is implemented in animatedbuilder AnimatedWidget.build () method: call the property builder to generate widget, and finally realize the binding of animation and widget.

Realization of loading animation

After understanding the animation of flutter, combined with the previous analysis of animation loading process, loading animation can be divided into three stages. We can rely on tween class and specify the range of values from 0.0 to 3.0. Of course, we can only use animationcontroller to specify the values of Lowerbound and uperbound to 0.0 and 3.0 respectively. The reason why curvedanimation is not used here is that the arc loaded with animation is linear, there is no acceleration and deceleration, so it is unnecessary to use it.

The large arc can be realized. Let’s look at the internal small arc again. If we observe it carefully, we will find that the change law of the small arc is completely consistent with that of the large arc, but the starting position of the small arc is in the negative direction of the X axis, which is exactly 180 degrees different from the large arc, that is, π radian. While drawing a large arc, the angle of the starting point of the small arc can be easily calculated (that is, the angle of the starting point of the large arc + π radian).

At this point, the realization idea of the whole animation is clear

  1. A widget for custom loading animation, inherited from the custompaint class.
  2. Use animationcontroller, Tween to create animation, the animation value range from 0.0 to 3.0, and set the animation to repeat. Each increment of animation interpolation by 1.0 represents a stage of animation execution.
  3. Inherit the custompainter class and implement the paint () method to draw arcs. According to the interpolation of the animation, which stage of the animation belongs to is judged. Then the starting point of the arc and the swept angle are calculated to draw two arcs.

The following is the key code for loading animation:

import 'dart:math';

 import 'package:flutter/material.dart';


 class WubaLoadingWidget extends StatefulWidget {

  @override

  _WubaLoadingWidgetState createState() => _WubaLoadingWidgetState();

 }


 class _WubaLoadingWidgetState extends State<WubaLoadingWidget>

  with SingleTickerProviderStateMixin {

  AnimationController _animationController;

  Animation<double> _animation;


  @override

  void initState() {

  super.initState();

  _animationController = new AnimationController(

   //You can specify Lowerbound and upperbound, and use the animationcontroller object

   // lowerBound: 0.0,

   // upperBound: 3.0,

   vsync: this,

   duration: const Duration(milliseconds: 1500),

  );

  _animation = Tween(begin: 0.0, end: 3.0)

   .animate(_animationController);

  A kind of animationController.forward (); // perform animation

  A kind of animationController.repeat (); // set animation loop execution

  }


  @override

  void dispose() {

  //Call dispose() method to release animation resources

  _animationController.dispose();

  super.dispose();

  }


  @override

  Widget build(BuildContext context) {

  return AnimatedBuilder(

   animation: _animationController,

   builder: (BuildContext context, Widget child) {

   return Container(

    child: CustomPaint(

    painter: _LoadingPaint(

     value: _animation.value,

    ),

    ),

   );

   },

  );

  }

 }


 class _LoadingPaint extends CustomPainter {

  final double value;

  final Paint _ Outerpaint; // paint of large arc

  final Paint _ Innerpaint; // paint for small arcs


  _LoadingPaint({

  this.value,

  });


  @override

  void paint(Canvas canvas, Size size) {

  double startAngle = 0;

  double sweepAngle = 0;

  //The first stage of animation: the starting point of the arc is 0 degrees, and the angle of the end point is increasing

  if (value <= 1.0) {

   startAngle = 0;

   sweepAngle = value * pi;

  }

  //The second stage of animation: the arc swept through the arc is π radian (180 degrees). The starting point and end point rotate clockwise together, and the total rotation is π radian

  else if (value <= 2.0) {

   startAngle = (value - 1) * pi;

   sweepAngle = pi;

  }

  //The third stage of the animation: the end point of the arc remains unchanged, and the starting point rotates clockwise from the negative direction of the x-axis until the starting point also reaches the positive direction of the x-axis

  else {

   startAngle = pi + (value - 2) * pi;

   sweepAngle = (3 - value) * pi;

  }

  //Draw the large arc of the outer ring

  canvas.drawArc(outerRect, startAngle, sweepAngle, false, _outerPaint);

  //Draw a small arc of the inner circle

  canvas.drawArc(innerRect, startAngle + pi, sweepAngle, false, _innerPaint);

  }


  @override

  bool shouldRepaint(CustomPainter oldDelegate) {

  return true;

  }

 }

summary

Flutter’s canvas, paint and Android API are very similar, and the basic ideas are the same, which is easier for Android students to master.

Compared with Android, the implementation of animation in fluent is clearer, simpler and easier to use. Animatedbuilder class cleverly integrates UI and animation, separating UI and animation responsibilities. This idea is worth learning. There are also route transition animation, hero animation, animated switcher, etc. students who need to find relevant information.

If you need to customize some personalized loading animation, we recommend an open source GitHub project: fluent_ Spinkit, this plug-in provides many common loading animation effects.

Well, the above is the whole content of this article, I hope the content of this article has a certain reference learning value for your study or work, thank you for your support to developeppaer.