上一篇文章我们了解了Flutter的动画基础:Flutter进阶—解析动画,这一篇文章我们就来实现一个图表的动画效果。

首先,我们需要创建一个新项目myapp,然后把main.dart的内容替换成下面的代码

import 'package:flutter/material.dart';
import 'dart:math';void main() {runApp(new MyApp());
}class MyApp extends StatelessWidget {@overrideWidget 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> {// Random([int seed ]):创建一个随机数生成器final random = new Random();int dataSet;void changeData() {setState(() {dataSet = random.nextInt(100);});}@overrideWidget build(BuildContext context) {return new Scaffold(body: new Center(child: new Text('数据集:$dataSet'),),floatingActionButton: new FloatingActionButton(onPressed: changeData,child: new Icon(Icons.refresh),),);}
}

启动项目后,应用程序会显示一个居中的文本标签,显示“数据集:null”和浮动按钮来刷新数据。

我们的应用程序生成的树结构如下图所示,您可以看到,虽然控件概念相当广泛,但每个具体的控件类型通常具有非常重要的责任。

通过定义用户界面的不可变的控件树,修改用户界面的唯一方法是重建树,当下一帧到期时告诉Flutter一个子树所依赖的一些状态已经改变了。这种状态依赖的子树的根必须是StatefulWidget,一个StatefulWidget不是可变的,但是它的子树是由State对象构建的。Flutter在构建期间通过树重建保留State对象并将其附加到新树中的各自的控件,然后,它们确定该控件的子树是如何构建的。在我们的应用程序中,MyHomePage是以_MyHomePageState为其状态的StatefulWidget,每当用户按下按钮时,我们执行一些代码来更改_MyHomePageState。我们已经用setState划分了这个变化,以便Flutter可以进行内部管理,并调度控件树进行重建。当发生这种情况时,_MyHomePageState将构建一个稍微不同的子树,这个子树以新的MyHomePage实例为根。

不可变的控件和状态依赖的子树是Flutter提供的主要工具,用于处理响应异步事件(比如按钮、定时器刻度或输入数据)的复杂用户界面中的状态管理的复杂性。

我们的应用程序将保持简单的控件结构,但我们会做一些动画定制图形,第一步是用一个非常简单的图表替换每个数据集的文本显示。由于数据集当前仅有一个在0~100之间数字,所以图表将是一个带有单个条形的条形图,其高度由该数字确定,我们将使用初始值50来避免高度为null。

import 'package:flutter/material.dart';
import 'dart:math';void main() {runApp(new MyApp());
}class MyApp extends StatelessWidget {@overrideWidget 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> {// Random([int seed ]):创建一个随机数生成器final random = new Random();int dataSet = 50;void changeData() {setState(() {dataSet = random.nextInt(100);});}@overrideWidget build(BuildContext context) {return new Scaffold(body: new Center(child: new CustomPaint(size: new Size(200.0, 100.0),painter: new BarChartPainter(dataSet.toDouble()))),floatingActionButton: new FloatingActionButton(onPressed: changeData,child: new Icon(Icons.refresh),),);}
}// CustomPaint:是将绘画委托给CustomPainter策略的控件
class BarChartPainter extends CustomPainter {static const barWidth = 10.0;BarChartPainter(this.barHeight);final double barHeight;/*void paint(Canvas canvas,Size size)当对象需要绘制时调用,它给出Canvas的坐标空间,使得原点位于框的左上角,框的面积是size参数的大小*/@overridevoid paint(Canvas canvas, Size size) {final paint = new Paint()..color = Colors.blue[400]..style = PaintingStyle.fill;// drawRect:使用给定的Paint绘制一个矩形,是否填充或描边(或两者)是由Paint.style控制canvas.drawRect(// Rect.fromLTWH(double left, double top, double width, double height):// 从左上角和上边缘构造一个矩形,并设置其宽度和高度new Rect.fromLTWH(size.width-barWidth/2.0,size.height-barHeight,barWidth,barHeight),paint);}/*bool shouldRepaint(CustomPainter,oldDelegate)当定制绘画委托类的新实例被提供给RenderCustomPaint对象时,或任何时候使用自定义绘画委托类的新实例创建新的CustomPaint对象(这相当于同一件事,因为后者是以前者实施)*/@overridebool shouldRepaint(BarChartPainter old) => barHeight != old.barHeight;
}

下一步是添加动画,每当数据集发生变化时,我们希望该栏可以平滑而不是突然地改变高度。Flutter有一个AnimationController的概念,用于编排动画,通过注册一个监听器,我们被告知当动画值(0.0~1.0)改变时。每当发生这种情况,我们可以像以前一样调用setState并更新_MyHomePageState。

import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
import 'dart:math';
import 'dart:ui' show lerpDouble;void main() {runApp(new MyApp());
}class MyApp extends StatelessWidget {@overrideWidget 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 {// Random([int seed ]):创建一个随机数生成器final random = new Random();int dataSet = 50;AnimationController animation;double startHeight;double currentHeight;double endHeight;/*@protected@mustCallSupervoid initState()将此对象插入树中时调用该框架将为其创建的每个State对象精确地调用此方法一次*/@overridevoid initState() {super.initState();/*AnimationController({double value,Duration duration,String debugLabel,double lowerBound: 0.0,double upperBound: 1.0,TickerProvider vsync})创建动画控制器*/animation = new AnimationController(// 这个动画应该持续的时间长短duration: const Duration(milliseconds: 300),vsync: this)/*void addListener(VoidCallback listener)每次动画值更改时调用监听器可以使用removeListener删除监听器*/..addListener((){setState((){/*double lerpDouble(num a,num b,double t)在两个数字之间进行线性内插return a + (b - a) * t;*/currentHeight = lerpDouble(startHeight,endHeight,animation.value);});});startHeight = 0.0;currentHeight = 0.0;endHeight = dataSet.toDouble();// 开始向前运行这个动画(朝向最后)animation.forward();}/*@overridevoid dispose()当该对象永久从树中删除时调用当该State对象永远不会再次构建时,该框架调用此方法框架调用dispose后,该State对象被视为已卸载,并且mounted属性为false,此时调用setState是一个错误生命周期的这个阶段是终点:没有办法重新安装dispose的State对象*/@overridevoid dispose() {animation.dispose();super.dispose();}void changeData() {setState(() {startHeight = currentHeight;dataSet = random.nextInt(100);endHeight = dataSet.toDouble();animation.forward(from: 0.0);});}@overrideWidget build(BuildContext context) {return new Scaffold(body: new Center(child: new CustomPaint(size: new Size(200.0, 100.0),painter: new BarChartPainter(currentHeight))),floatingActionButton: new FloatingActionButton(onPressed: changeData,child: new Icon(Icons.refresh),),);}
}// CustomPaint:是将绘画委托给CustomPainter策略的控件
class BarChartPainter extends CustomPainter {static const barWidth = 10.0;BarChartPainter(this.barHeight);final double barHeight;/*void paint(Canvas canvas,Size size)当对象需要绘制时调用,它给出Canvas的坐标空间,使得原点位于框的左上角,框的面积是size参数的大小*/@overridevoid paint(Canvas canvas, Size size) {final paint = new Paint()..color = Colors.blue[400]..style = PaintingStyle.fill;// drawRect:使用给定的Paint绘制一个矩形,是否填充或描边(或两者)是由Paint.style控制canvas.drawRect(// Rect.fromLTWH(double left, double top, double width, double height):// 从左上角和上边缘构造一个矩形,并设置其宽度和高度new Rect.fromLTWH(size.width-barWidth/2.0,size.height-barHeight,barWidth,barHeight),paint);}/*bool shouldRepaint(CustomPainter,oldDelegate)当定制绘画委托类的新实例被提供给RenderCustomPaint对象时,或任何时候使用自定义绘画委托类的新实例创建新的CustomPaint对象(这相当于同一件事,因为后者是以前者实施)*/@overridebool shouldRepaint(BarChartPainter old) => barHeight != old.barHeight;
}

上面代码中的lerpDouble函数比较难理解,代入参数之后计算结果如下图。

数据从一开始的0.0到达50.0时,花费了10个时间点。再到达52时,则花费了16个时间点。因此大约得出的结论时,在我们的应用程序中,数据变化越小,花费的时间点越多。

现在程序已经变得复杂性,我们的数据集仍然只是一个数字,设置动画控制所需的代码是一个小问题,因为当我们获得更多的图表数据时,它不会被分解。真正的问题是变量startHeight、currentHeight和endHeight,反映了对数据集和动画值所做的更改,并在三个不同的地方更新。

我们需要一个概念来处理这个混乱的情况。

未完待续~~~

Flutter进阶—实现动画效果(一)相关推荐

  1. Flutter进阶—实现动画效果(四)

    在上一篇文章:Flutter进阶-实现动画效果(三)中,实现了一个随机高度.颜色的条形.这一篇文章我们会实现多个条形,同样是随机高度.颜色. 首先在bar.dart中创建BarChart类,并使用固定 ...

  2. Flutter进阶—实现动画效果(三)

    在上一篇文章:Flutter进阶-实现动画效果(二)的最后,我们实现了一个控件,其中包含各种布局和状态处理控件.以及使用自定义的动画感知绘图代码绘制单个Bar的控件.还有一个浮动按钮控件,用于启动条形 ...

  3. Flutter进阶—实现动画效果(二)

    在上一篇文章:Flutter进阶-实现动画效果(一)的最后,我们说到需要一个处理程序混乱的概念.在这一篇文章中,我们会引入补间,它是构建动画代码的一个非常简单的概念,主要作用是用面向对象的方法替代之前 ...

  4. Flutter进阶—实现动画效果(十)

    前面的两篇文章[动画效果(八).动画效果(九)]中,我们只需要统计产品和地区,如果现在增加一个统计项目--销售渠道,那么使用之前的堆叠条形图和分组条形图都不适合.我们可以将两者结合,使用分组+堆叠条形 ...

  5. Flutter进阶—实现动画效果(八)

    通过学习前面的文章,我们现在终于能为更复杂的图表制作动画效果了[手动斜眼笑].接着上一篇文章讲,如果公司的产品销往全国各地,那么我们的图表要展示的内容就需要加上地区.我们可以使用堆叠条形图来试试效果, ...

  6. Flutter进阶—实现动画效果(七)

    我们假设一种情况,如果应用程序使用条形图显示给定年份的产品类别的销售额,用户可以选择另一年,然后该应用程序将动画到该年的条形图.如果产品类别在两年内是相同的,或者恰好是相同的,除了在其中一个图表中右侧 ...

  7. Flutter进阶—实现动画效果(五)

    在本篇文章开始前,我们先来回顾一下之前我们都做了哪些事情.在第一篇文章中,我们在动画值更改时调用double lerpDouble(num a, num b, double t)重新绘制条形.在第二篇 ...

  8. Flutter进阶—实现动画效果(九)

    在上一篇文章中,我们实现了统计每个产品和地区的销售额,如果现在需要统计每个产品和地区所占市场份额的百分比,那么使用堆叠条形图是不合适的,我们可以使用分组条形图,因为它可以同时在两个类别维度上进行定量比 ...

  9. Flutter进阶—实现动画效果(六)

    在上一篇文章中,我们之前对BarChart.lerp的定义并不是高效的,我们正在创建的Bar实例,仅作为Bar.lerp的参数给出,并且针对动画参数t的每个值重复出现.每秒60帧,这意味着可能很多Ba ...

最新文章

  1. CADisplayLink 及定时器的使用
  2. 光伏组件清洗的7大注意事项
  3. mysql 双节点主从搭建_MySQL Replication, 主从和双主配置
  4. VS Code的Error: Running the contributed command: ‘_workbench.downloadResource‘ failed解决
  5. 苹果收购人工智能初创公司Voysis以改善语音助手Siri功能
  6. Codejock Xtreme ToolkitPro MFC 使用
  7. 形态学图像处理之边界提取与跟踪
  8. 永凯APS生产排程软件同时考虑物料及产能
  9. 带文本的标签自动生成font标签
  10. 阵列卡的全称叫磁盘阵列卡 是用来做 RAID
  11. 手绘机器学习全流程,教你如何实现模型训练
  12. 长 三 角 制 造 - 香 港 服 务
  13. 生物信息百Jia软件(28):canu
  14. 【转】每个程序员应该阅读的10本经典书籍
  15. python 的 轮子
  16. 1小时用马克笔画出时尚女郎
  17. JAVA中调用阿里云语音通知Api并接收消息回执
  18. 全网招募P图高手!阿里巴巴持续训练鉴假AI
  19. java sdk7.0下载_jdk7.0下载(Java SE Development Kit 7) 7u80官方版 win32
  20. Composition API TypeError: Cannot add property xxx, object is not extensible

热门文章

  1. 牛客寒假算法基础训练营3
  2. ~~求欧拉函数(附模板题)
  3. C源程序括号匹配检查(C语言)
  4. 【干货】sql-labs、请求方式、注入类型、拼接方式
  5. Ubuntu 20.04 安装CUDA11.1 和cudnn 8.0.5
  6. python 自定义向量化(vectorized)操作函数
  7. Linux卸载系统中自带java、jdk等
  8. 【Qt教程】1.2 - Qt5 新建工程
  9. c/c++教程 - 1.1 代码注释
  10. Java基础难点补充