Flutter Advanced Realization Animation Effect (2)

  • 2021-10-13 08:33:56
  • OfStack

At the end of the last article, Flutter Advanced-Implementing Animation Effects (1), we talked about the need for a concept of handling program confusion. In this 1 article, we introduce complement, a very simple concept for building animation code, which is mainly used to replace the previous process-oriented method with an object-oriented method. tween is a value that describes the path between two points in the space of other values, such as the animation value of a bar graph running from 0 to 1.

Complement represents an object of type Tween in Dart


abstract class Tween<T> {
 final T begin;
 final T end;

 Tween(this.begin, this.end);

 T lerp(double t);
}

The term lerp comes from the field of computer graphics and is an abbreviation for linear interpolation (as a noun) and linear interpolation (as a verb). The parameter t is an animated value, and the complement should range from begin (when t is 0) to end (when t is 1).

The Tween class of FlutterSDK is very similar to Dart, but a concrete class that supports variations of begin and end. We can use a single Tween to organize the code for dealing with bar height.


import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
import 'dart:math';

void main() {
 runApp(new MyApp());
}

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
  return new MaterialApp(
    title: 'Flutter Demo',
    home: new MyHomePage(),
  );
 }
}

class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
 final random = new Random();
 int dataSet = 50;
 AnimationController animation;
 Tween<double> tween;

 @override
 void initState() {
  super.initState();
  animation = new AnimationController(
    duration: const Duration(milliseconds: 300),
    vsync: this
  );
  // Tween({T begin, T end }) : Create tween (Make up the room) 
  tween = new Tween<double>(begin: 0.0, end: dataSet.toDouble());
  animation.forward();
 }

 @override
 void dispose() {
  animation.dispose();
  super.dispose();
 }

 void changeData() {
  setState(() {
   dataSet = random.nextInt(100);
   tween = new Tween<double>(
    /*
    @override
    T evaluate(
     Animation<double> animation
    )
     Returns the interpolation of the current value of the given animation 
     When the animation values are 0.0 Or 1.0 This method returns begin And end
     */
    begin: tween.evaluate(animation),
    end: dataSet.toDouble()
   );
   animation.forward(from: 0.0);
  });
 }

 @override
 Widget build(BuildContext context) {
  return new Scaffold(
    body: new Center(
     child: new CustomPaint(
      size: new Size(200.0, 100.0),
      /*
      Animation<T> animate(
       Animation<double> parent
      )
       Return 1 A new animation driven by the given animation, but it assumes the value determined by the object 
       */
      painter: new BarChartPainter(tween.animate(animation))
     )
    ),
    floatingActionButton: new FloatingActionButton(
      onPressed: changeData,
      child: new Icon(Icons.refresh),
    ),
  );
 }
}

class BarChartPainter extends CustomPainter {
 static const barWidth = 10.0;

 BarChartPainter(Animation<double> animation)
   : animation = animation,
    super(repaint: animation);

 final Animation<double> animation;

 @override
 void paint(Canvas canvas, Size size) {
  final barHeight = animation.value;
  final paint = new Paint()
   ..color = Colors.blue[400]
   ..style = PaintingStyle.fill;
  canvas.drawRect(
    new Rect.fromLTWH(
      size.width-barWidth/2.0,
      size.height-barHeight,
      barWidth,
      barHeight
    ),
    paint
  );
 }

 @override
 bool shouldRepaint(BarChartPainter old) => false;
}

We use Tween to wrap the bar height animation endpoint in a value, which is fully interfaced with AnimationController and CustomPainter, because the Flutter framework now marks CustomPaint for redrawing at each animation time point, instead of marking the entire MyHomePage subtree for reconstruction, relayout and redrawing. These are displays of improvements, but the concept of complement is more than that, it provides a structure for organizing our ideas and code.

Going back to our code, we need an Bar type and an BarTween to animate it. We extracted the bar-related classes into the bar. dart file and placed them in the main. dart sibling directory.


import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
import 'dart:ui' show lerpDouble;

class Bar {
 Bar(this.height);
 final double height;

 static Bar lerp(Bar begin, Bar end, double t) {
  return new Bar(lerpDouble(begin.height, end.height, t));
 }
}

class BarTween extends Tween<Bar> {
 BarTween(Bar begin, Bar end) : super(begin: begin, end: end);

 @override
 Bar lerp(double t) => Bar.lerp(begin, end, t);
}

class BarChartPainter extends CustomPainter {
 static const barWidth = 10.0;

 BarChartPainter(Animation<Bar> animation)
   : animation = animation,
    super(repaint: animation);

 final Animation<Bar> animation;

 @override
 void paint(Canvas canvas, Size size) {
  final bar = animation.value;
  final paint = new Paint()
   ..color = Colors.blue[400]
   ..style = PaintingStyle.fill;
  canvas.drawRect(
    new Rect.fromLTWH(
      size.width-barWidth/2.0,
      size.height-bar.height,
      barWidth,
      bar.height
    ),
    paint
  );
 }

 @override
 bool shouldRepaint(BarChartPainter old) => false;
}


We follow the convention of FlutterSDK to define the static method BarTween. lerp of the Bar class. There is no double. lerp in DartSDK, so we use the lerpDouble function in the dart: ui package to achieve the same effect.

Now our application can be redisplayed with a bar chart.


import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
import 'dart:math';
import 'bar.dart';

void main() {
 runApp(new MyApp());
}

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
  return new MaterialApp(
    title: 'Flutter Demo',
    home: new MyHomePage(),
  );
 }
}

class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
 final random = new Random();
 AnimationController animation;
 BarTween tween;

 @override
 void initState() {
  super.initState();
  animation = new AnimationController(
    duration: const Duration(milliseconds: 300),
    vsync: this
  );
  tween = new BarTween(new Bar(0.0), new Bar(50.0));
  animation.forward();
 }

 @override
 void dispose() {
  animation.dispose();
  super.dispose();
 }

 void changeData() {
  setState(() {
   tween = new BarTween(
    tween.evaluate(animation),
    new Bar(100.0 * random.nextDouble()),
   );
   animation.forward(from: 0.0);
  });
 }

 @override
 Widget build(BuildContext context) {
  return new Scaffold(
    body: new Center(
     child: new CustomPaint(
      size: new Size(200.0, 100.0),
      painter: new BarChartPainter(tween.animate(animation))
     )
    ),
    floatingActionButton: new FloatingActionButton(
      onPressed: changeData,
      child: new Icon(Icons.refresh),
    ),
  );
 }
}

To be continued.


Related articles: