Flutter Animation动画
一,概述
Flutter
动画库的核心类是Animation
对象,它生成指导动画的值,Animation
对象指导动画的当前状态(例如,是开始、停止还是向前或者向后移动),但它不知道屏幕上显示的内容。动画类型分为两类:
- 补简动画(Tween),定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过渡到结束点。Tween是一个无状态(stateless)对象,需要begin和end值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的。
- 基于物理动画,运动被模拟与真实世界行为相似,例如,当你掷球时,它何处落地,取决于抛球速度有多快、球有多重、距离地面有多远。类似地,将连接在弹簧上的球落下(并弹起)与连接到绳子的球放下的方式也是不同。
在Flutter
中的动画系统基于Animation
对象的。widget
可以在build
函数中读取Animation
对象的当前值,并且可以监听动画的状态改变。
二,Flutter动画介绍
Animation
Animation 是 Flutter 动画库中的核心类,它会插入指导动画生成的值。 Animation 对象知道一个动画当前的状态(例如开始、 停止、 播放、 回放), 但它不知道屏幕上绘制的是什么, 因为 Animation 对象只是提供一个值表示当前需要展示的动画, UI 如何绘制出图形完全取决于 UI 自身如何在渲染和 build() 方法里处理这个值, 当然也可以不做处理。 Animation<double>
是一个比较常用的Animation类, 泛型也可以支持其它的类型,比如: Animation<Color>
或 Animation<Size>
。 Animation 对象就是会在一段时间内依次生成一个区间之间值的类, 它的输出可以是线性的、曲线的、一个步进函数或者任何其他可以设计的映射 比如:CurvedAnimation。
AnimationController
AnimationController 是一个动画控制器, 它控制动画的播放状态, 如例子里面的: controller.forward()
就是控制动画"向前"播放。 所以构建 AnimationController 对象之后动画并没有立刻开始执行。 在默认情况下, AnimationController 会在给定的时间内线性地生成从 0.0 到 1.0 之间的数字。 AnimationController 是一种特殊的 Animation 对象了, 它父类其实是一个 Animation<double>
, 当硬件准备好需要一个新的帧的时候它就会产生一个新的值。 由于 AnimationController 派生自 Animation <double>
,因此可以在需要 Animation 对象的任何地方使用它。 但是 AnimationController 还有其他的方法来控制动画的播放, 例如前面提到的 .forward()
方法启动动画。
AnimationController 生成的数字(默认是从 0.0 到 1.0) 是和屏幕刷新有关, 前面也提到它会在硬件需要一个新帧的时候产生新值。 因为屏幕一般都是 60 帧/秒, 所以它也通常一秒内生成 60 个数字。 每个数字生成之后, 每个 Animation 对象都会调用绑定的监听器对象。
Tween
Tween 本身表示的就是一个 Animation 对象的取值范围, 只需要设置开始和结束的边界值(值也支持泛型)。 它唯一的工作就是定义输入范围到输出范围的映射, 输入一般是 AnimationController 给出的值 0.0~1.0。 看下面的例子, 我们就能知道 animation 的 value 是怎么样通过 AnimationController 生成的值映射到 Tween 定义的取值范围里面的。
Tween.animation
通过传入 aniamtionController 获得一个_AnimatedEvaluation 类型的 animation 对象(基类为 Animation), 并且将 aniamtionController 和 Tween 对象传入了 _AnimatedEvaluation 对象。animation = new Tween(begin: 0.0, end: 300.0).animate(controller)...Animation<T> animate(Animation<double> parent) {return _AnimatedEvaluation<T>(parent, this);}
animation.value
方法即是调用_evaluatable.evaluate(parent)
方法, 而 _evaluatable 和 parent 分别为 Tween 对象和 AnimationController 对象。T get value => _evaluatable.evaluate(parent);....class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {_AnimatedEvaluation(this.parent, this._evaluatable);....
- 这里的 animation 其实就是前面的 AnimationController 对象, transform 方法里面的
animation.value
则就是 AnimationController 线性生成的 0.0~1.0 直接的值。 在 lerp 方法里面我们可以看到这个 0.0~1.0 的值被映射到了 begin 和 end 范围内了。T evaluate(Animation<double> animation) => transform(animation.value);T transform(double t) {if (t == 0.0)return begin;if (t == 1.0)return end;return lerp(t);}T lerp(double t) {assert(begin != null);assert(end != null);return begin + (end - begin) * t;}
Flutter 的"时钟"
那么 Flutter 是怎么样让这个动画在规定时间不断地绘制的呢?
- 首先看 Widget 引入的 SingleTickerProviderStateMixin 类。SingleTickerProviderStateMixin 是以 with 关键字引入的, 这是 dart 语言的 mixin 特性, 可以理解成"继承", 所以 widget 相当于是继承了SingleTickerProviderStateMixin。 所以在 AnimationController 对象的构造方法参数
vsync: this
, 我们看到了这个类的使用。 从 "vsync" 参数名意为"垂直帧同步"可以看出, 这个是绘制动画帧的"节奏器"。AnimationController({double value,this.duration,this.debugLabel,this.lowerBound = 0.0,this.upperBound = 1.0,this.animationBehavior = AnimationBehavior.normal,@required TickerProvider vsync,}) : assert(lowerBound != null),assert(upperBound != null),assert(upperBound >= lowerBound),assert(vsync != null),_direction = _AnimationDirection.forward {_ticker = vsync.createTicker(_tick);_internalSetValue(value ?? lowerBound);}
- 在 AnimationController 的构造方法中, SingleTickerProviderStateMixin 的父类 TickerProvider 会创建一个 Ticker, 并将_tick(TickerCallback 类型)回调方法绑定到了 这个 Ticker, 这样 AnimationController 就将回调方法 _tick 和 Ticker 绑定了。
@protected void scheduleTick({ bool rescheduling = false }) {assert(!scheduled);assert(shouldScheduleTick);_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling); }
而 Ticker 会在 start 函数内将_tick 被绑定到 SchedulerBinding 的帧回调方法内。 返回的_animationId 是 SchedulerBinding 给定的下一个动作回调的 ID, 可以根据_animationId 来取消 SchedulerBinding 上绑定的回调。
SchedulerBinding 则是在构造方法中将自己的 _handleBeginFrame 函数和 window 的 onBeginFrame 绑定了回调。 这个回调会在屏幕需要准备显示帧之前回调。
再回到 AnimationController 看它是如何控制 Animation 的值的。
void _tick(Duration elapsed) {_lastElapsedDuration = elapsed;final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;assert(elapsedInSeconds >= 0.0);_value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);if (_simulation.isDone(elapsedInSeconds)) {_status = (_direction == _AnimationDirection.forward) ?AnimationStatus.completed :AnimationStatus.dismissed;stop(canceled: false);}notifyListeners();_checkStatusChanged();}
在 AnimationController 的回调当中, 会有一个 Simulation 根据动画运行了的时间(elapsed) 来计算当前的的_value 值, 而且这个值还需要处于 Animation 设置的区间之内。 除了计算_value 值之外, 该方法还会更新 Animation Status 的状态, 判断是否动画已经结束。 最后通过 notifyListeners 和_checkStatusChanged 方法通知给监听器 value 和 AnimationStatus 的变化。 监听 AnimationStatus 值的变化有一个专门的注册方法 addStatusListener。
通过监听 AnimationStatus, 在动画开始或者结束的时候反转动画, 就达到了动画循环播放的效果。
...animation.addStatusListener((status) {if (status == AnimationStatus.completed) {controller.reverse();} else if (status == AnimationStatus.dismissed) {controller.forward();}});controller.forward();...
回顾一下这个动画绘制调用的顺序就是, window 调用 SchedulerBinding 的_handleBeginFrame 方法, SchedulerBinding 调用 Ticker 的_tick 方法, Ticker 调用 AnimationController 的_tick 的方法, AnimationContoller 通知监听器, 而监听器调用 widget 的 setStatus 方法来调用 build 更新, 最后 build 使用了 Animation 对象当前的值来绘制动画帧。
看到这里会有一个疑惑, 为什么监听器是注册在 Animation 上的, 监听通知反而由 AnimationController 发送?
还是看源码吧。
Animation<T> animate(Animation<double> parent) {return _AnimatedEvaluation<T>(parent, this);}class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {_AnimatedEvaluation(this.parent, this._evaluatable); }mixin AnimationWithParentMixin<T> {Animation<T> get parent;/// Listeners can be removed with [removeListener].void addListener(VoidCallback listener) => parent.addListener(listener); }
- 首先 Animation 对象是由 Tween 的 animate 方法生成的, 它传入了 AnimationController(Animation 的子类) 参数 作为 parent 参数, 然后我们发现返回的
_AnimatedEvaluation<T>
泛型对象 使用 mixin "继承" 了AnimationWithParentMixin<double>
, 最后我们看到 Animation 作为 AnimationWithParentMixin 的"子类"实现的 addListener 方法其实是将监听器注册到 parent 对象上了, 也就是 AnimationController。
三,动画示例
- 示例一
import 'package:flutter/material.dart'; import 'package:flutter/animation.dart';void main() {//运行程序runApp(LogoApp()); }class LogoApp extends StatefulWidget{@overrideState<StatefulWidget> createState(){return new _LogoAppState();} }//logo Widget ImageLogo = new Image(image: new AssetImage('images/logo.jpg'), );//with 是dart的关键字,混入的意思,将一个或者多个类的功能添加到自己的类无需继承这些类 //避免多重继承问题 //SingleTickerProviderStateMixin 初始化 animation 和 Controller的时候需要一个TickerProvider类型的参数Vsync //所依混入TickerProvider的子类 class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{//动画的状态,如动画开启,停止,前进,后退等Animation<double> animation;//管理者animation对象AnimationController controller;@overridevoid initState() {// TODO: implement initStatesuper.initState();//创建AnimationController//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。controller = new AnimationController(//时间是3000毫秒duration: const Duration(milliseconds: 3000),//vsync 在此处忽略不必要的情况vsync: this,);//补间动画animation = new Tween(//开始的值是0begin: 0.0,//结束的值是200end : 200.0,).animate(controller)//添加监听器..addListener((){//动画值在发生变化时就会调用setState(() {});});//只显示动画一次controller.forward();}@overrideWidget build(BuildContext context){return new MaterialApp(theme: ThemeData(primarySwatch: Colors.red),home: new Scaffold(appBar: new AppBar(title: Text("动画demo"),),body:new Center(child: new Container(//宽和高都是根据animation的值来变化height: animation.value,width: animation.value,child: ImageLogo,),),),);}@overridevoid dispose() {// TODO: implement disposesuper.dispose();//资源释放controller.dispose();} }
上面实现了图像在3000毫秒间从宽高是0变化到宽高是200,主要分为六部
- 混入
SingleTickerProviderStateMixin
,为了传入vsync
对象 - 初始化
AnimationController
对象 - 初始化
Animation
对象,并关联AnimationController
对象 - 调用
AnimationController
的forward
开启动画 widget
根据Animation
的value
值来设置宽高- 在
widget
的dispose()
方法中调用释放资源
最终效果如下:
注意:上面创建Tween
用了Dart
语法的级联符号animation = tween.animate(controller)..addListener(() {setState(() {// the animation object’s value is the changed state});});
等价于下面代码:
animation = tween.animate(controller); animation.addListener(() {setState(() {// the animation object’s value is the changed state});});
1.1.AnimatedWidget简化
使用
AnimatedWidget
对动画进行简化,使用AnimatedWidget
创建一个可重用动画的widget
,而不是用addListener()
和setState()
来给widget
添加动画。AnimatedWidget
类允许从setState()
调用中的动画代码中分离出widget
代码。AnimatedWidget
不需要维护一个State
对象了来保存动画。import 'package:flutter/material.dart'; import 'package:flutter/animation.dart';void main() {//运行程序runApp(LogoApp()); }class LogoApp extends StatefulWidget{@overrideState<StatefulWidget> createState(){return new _LogoAppState();} }//logo Widget ImageLogo = new Image(image: new AssetImage('images/logo.jpg'), );//抽象出来 class AnimatedLogo extends AnimatedWidget{AnimatedLogo({Key key,Animation<double> animation}):super(key:key,listenable:animation);@overrideWidget build(BuildContext context){final Animation<double> animation = listenable;return new MaterialApp(theme: ThemeData(primarySwatch: Colors.red),home: new Scaffold(appBar: new AppBar(title: Text("动画demo"),),body:new Center(child: new Container(//宽和高都是根据animation的值来变化height: animation.value,width: animation.value,child: ImageLogo,),),),);} }//with 是dart的关键字,混入的意思,将一个或者多个类的功能添加到自己的类无需继承这些类 //避免多重继承问题 //SingleTickerProviderStateMixin 初始化 animation 和 Controller的时候需要一个TickerProvider类型的参数Vsync //所依混入TickerProvider的子类 class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{//动画的状态,如动画开启,停止,前进,后退等Animation<double> animation;//管理者animation对象AnimationController controller;@overridevoid initState() {// TODO: implement initStatesuper.initState();//创建AnimationController//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。controller = new AnimationController(//时间是3000毫秒duration: const Duration(milliseconds: 3000),//vsync 在此处忽略不必要的情况vsync: this,);//补间动画animation = new Tween(//开始的值是0begin: 0.0,//结束的值是200end : 200.0,).animate(controller);//添加监听器//只显示动画一次controller.forward();}@overrideWidget build(BuildContext context){return AnimatedLogo(animation: animation);}@overridevoid dispose() {// TODO: implement disposesuper.dispose();//资源释放controller.dispose();} }
可以发现
AnimatedWidget
中会自动调用addListener
和setState()
,_LogoAppState
将Animation
对象传递给基类并用animation.value
设置Image宽高。1.2.监视动画
在平时开发,我们知道,很多时候都需要监听动画的状态,好像完成、前进、倒退等。在
Flutter
中可以通过addStatusListener()
来得到这个通知,以下代码添加了动画状态//补间动画animation = new Tween(//开始的值是0begin: 0.0,//结束的值是200end : 200.0,).animate(controller)//添加动画状态..addStatusListener((state){return print('$state');});//添加监听器
运行代码会输出下面结果:
I/flutter (16745): AnimationStatus.forward //动画开始 Syncing files to device KNT AL10... I/zygote64(16745): Do partial code cache collection, code=30KB, data=25KB I/zygote64(16745): After code cache collection, code=30KB, data=25KB I/zygote64(16745): Increasing code cache capacity to 128KB I/flutter (16745): AnimationStatus.completed//动画完成
下面那就运用
addStatusListener()
在开始或结束反转动画。那就产生循环效果://补间动画animation = new Tween(//开始的值是0begin: 0.0,//结束的值是200end : 200.0,).animate(controller)//添加动画状态..addStatusListener((state){//如果动画完成了if(state == AnimationStatus.completed){//开始反向这动画controller.reverse();} else if(state == AnimationStatus.dismissed){//开始向前运行着动画controller.forward();}});//添加监听器
效果如下:
1.3.用AnimatedBuilder重构
上面的代码存在一个问题:更改动画需要更改显示
Image
的widget
,更好的解决方案是将职责分离:- 显示图像
- 定义
Animation
对象 - 渲染过渡效果 这时候可以借助
AnimatedBuilder
类完成此分离。AnimatedBuilder
是渲染树中的一个独立的类,与AnimatedWidget
类似,AnimatedBuilder
自动监听来自Animation
对象的通知,并根据需要将该控件树标记为脏(dirty),因此不需要手动调用addListener()
//AnimatedBuilder class GrowTransition extends StatelessWidget{final Widget child;final Animation<double> animation;GrowTransition({this.child,this.animation});@overrideWidget build(BuildContext context){return new MaterialApp(theme: ThemeData(primarySwatch: Colors.red),home: new Scaffold(appBar: new AppBar(title: Text("动画demo"),),body:new Center(child: new AnimatedBuilder(animation: animation,builder: (BuildContext context,Widget child){return new Container(//宽和高都是根据animation的值来变化height: animation.value,width: animation.value,child: child,);},child: child,),),),);}class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{//动画的状态,如动画开启,停止,前进,后退等Animation animation;//管理者animation对象AnimationController controller;@overridevoid initState() {// TODO: implement initStatesuper.initState();//创建AnimationController//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。controller = new AnimationController(//时间是3000毫秒duration: const Duration(milliseconds: 3000),//vsync 在此处忽略不必要的情况vsync: this,);final CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);//补间动画animation = new Tween(//开始的值是0begin: 0.0,//结束的值是200end : 200.0,).animate(curve) // //添加动画状态..addStatusListener((state){//如果动画完成了if(state == AnimationStatus.completed){//开始反向这动画controller.reverse();} else if(state == AnimationStatus.dismissed){//开始向前运行着动画controller.forward();}});//添加监听器//只显示动画一次controller.forward();}@overrideWidget build(BuildContext context){//return AnimatedLogo(animation: animation);return new GrowTransition(child:ImageLogo,animation: animation);}@overridevoid dispose() {// TODO: implement disposesuper.dispose();//资源释放controller.dispose();} }
上面代码有一个迷惑的问题是,
child
看起来好像是指定了两次,但实际发生的事情是,将外部引用的child
传递给AnimatedBuilder
,AnimatedBuilder
将其传递给匿名构造器,然后将该对象用作其子对象。最终的结果是AnimatedBuilder
插入到渲染树中的两个Widget
之间。最后,在initState()
方法创建一个AnimationController
和一个Tween
,然后通过animate()
绑定,在build
方法中,返回带有一个Image
为子对象的GrowTransition
对象和一个用于驱动过渡的动画对象。如果只是想把可复用的动画定义成一个widget
,那就用AnimatedWidget
。1.4.并行动画
很多时候,一个动画需要两种或者两种以上的动画,在
Flutter
也是可以实现的,每一个Tween
管理动画的一种效果,如:final AnimationController controller =new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);final Animation<double> sizeAnimation =new Tween(begin: 0.0, end: 300.0).animate(controller);final Animation<double> opacityAnimation =new Tween(begin: 0.1, end: 1.0).animate(controller);
可以通过
sizeAnimation.Value
来获取大小,通过opacityAnimation.value
来获取不透明度,但AnimatedWidget
的构造函数只能接受一个动画对象,解决这个问题,需要动画的widget
创建了自己的Tween
对象,上代码://AnimatedBuilder class GrowTransition extends StatelessWidget {final Widget child;final Animation<double> animation;GrowTransition({this.child, this.animation});static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);static final _sizeTween = new Tween<double>(begin: 0.0, end: 200.0);@overrideWidget build(BuildContext context) {return new MaterialApp(theme: ThemeData(primarySwatch: Colors.red),home: new Scaffold(appBar: new AppBar(title: Text("动画demo"),),body: new Center(child: new AnimatedBuilder(animation: animation,builder: (BuildContext context, Widget child) {return new Opacity(opacity: _opacityTween.evaluate(animation),child: new Container(//宽和高都是根据animation的值来变化height: _sizeTween.evaluate(animation),width: _sizeTween.evaluate(animation),child: child,),);},child: child,),),),);} }class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {//动画的状态,如动画开启,停止,前进,后退等Animation<double> animation;//管理者animation对象AnimationController controller;@overridevoid initState() {// TODO: implement initStatesuper.initState();//创建AnimationController//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。controller = new AnimationController(//时间是3000毫秒duration: const Duration(milliseconds: 3000),//vsync 在此处忽略不必要的情况vsync: this,);//新增animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)..addStatusListener((state) {//如果动画完成了if (state == AnimationStatus.completed) {//开始反向这动画controller.reverse();} else if (state == AnimationStatus.dismissed) {//开始向前运行着动画controller.forward();}}); //添加监听器//只显示动画一次controller.forward();}@overrideWidget build(BuildContext context) {return new GrowTransition(child:ImageLogo,animation: animation);}@overridevoid dispose() {// TODO: implement disposesuper.dispose();//资源释放controller.dispose();} }
可以看到在
GrowTransition
定义两个Tween
动画,并且加了不透明Opacity
widget,最后在initState
方法中修改增加一句animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)
,最后的动画效果:注意:可以通过改变
Curves.easeIn
值来实现非线性运动效果。 - 混入
2.自定义动画
示例2:
2.1.自定义小球
class _bollView extends CustomPainter{//颜色Color color;//数量int count;//集合放动画List<Animation<double>> ListAnimators;_bollView({this.color,this.count,this.ListAnimators});@overridevoid paint(Canvas canvas,Size size){//绘制流程double boll_radius = (size.width - 15) / 8;Paint paint = new Paint();paint.color = color;paint.style = PaintingStyle.fill;//因为这个wiaget是80 球和球之间相隔5for(int i = 0; i < count;i++){double value = ListAnimators[i].value;//确定圆心 半径 画笔//第一个球 r//第二个球 5 + 3r//第三个球 15 + 5r//第四个球 30 + 7r//半径也是随着动画值改变canvas.drawCircle(new Offset((i+1) * boll_radius + i * boll_radius + i * 5,size.height / 2), boll_radius * (value > 1 ? (2 - value) : value), paint);}}//刷新是否重绘@overridebool shouldRepaint(CustomPainter oldDelegate){return oldDelegate != this;} }
2.2.配置小球属性
class MyBalls extends StatefulWidget{Size size;Color color;int count;int seconds;//默认四个小球 红色MyBalls({this.size,this.seconds : 400,this.color :Colors.redAccent,this.count : 4});@overrideState<StatefulWidget> createState(){return MyBallsState();} }
2.3.创建动画
//继承TickerProviderStateMixin,提供Ticker对象 class MyBallsState extends State<MyBalls> with TickerProviderStateMixin {//动画集合List<Animation<double>>animatios = [];//控制器集合List<AnimationController> animationControllers = [];//颜色Animation<Color> colors;@overridevoid initState(){super.initState();for(int i = 0;i < widget.count;i++){//创建动画控制器AnimationController animationController = new AnimationController(vsync: this,duration: Duration(milliseconds: widget.count * widget.seconds));//添加到控制器集合animationControllers.add(animationController);//颜色随机colors = ColorTween(begin: Colors.red,end:Colors.green).animate(animationController);//创建动画 每个动画都要绑定控制器Animation<double> animation = new Tween(begin: 0.1,end:1.9).animate(animationController);animatios.add(animation);}animatios[0].addListener((){//刷新setState(() {});});//延迟执行var delay = (widget.seconds ~/ (2 * animatios.length - 2));for(int i = 0;i < animatios.length;i++){Future.delayed(Duration(milliseconds: delay * i),(){animationControllers[i]..repeat().orCancel;});}}@overrideWidget build(BuildContext context){return new CustomPaint(//自定义画笔painter: _bollView(color: colors.value,count: widget.count,ListAnimators : animatios),size: widget.size,);}//释放资源@overridevoid dispose(){super.dispose();animatios[0].removeListener((){setState(() {});});animationControllers[0].dispose();} }
2.4.调用
class Ball extends StatelessWidget{@overrideWidget build(BuildContext context){return MaterialApp(home: Scaffold(appBar: AppBar(title: Text('Animation demo'),),body: Center(child: MyBalls(size: new Size(80.0,20.0)),),),);} }
四,总结
本篇文章从简单的例子出发, 并且结合了源码, 分析了 Flutter 动画实现的原理。Flutter 以硬件设备刷新为驱动, 驱使 widget 依据给定的值生成新动画帧, 从而实现了动画效果。
链接:
1. https://juejin.im/post/5cdbbc01f265da037b6134d9
2.https://juejin.im/post/5c617e34f265da2d90581613
Flutter Animation动画相关推荐
- 【Flutter】Animation 动画 ( Flutter 动画类型 | Flutter 动画的核心类 )
文章目录 一.Flutter 动画类型 二.Flutter 动画的核心类 三.相关资源 Flutter Animation 动画 : Flutter 动画类型 为 Widget 组件添加动画 为动画添 ...
- 【Flutter】Animation 动画 ( AnimatedBuilder 动画使用流程 | 创建动画控制器 | 创建动画 | 创建动画作用的组件 | 关联动画与组件 | 动画执行 )
文章目录 ◯.AnimatedBuilder 引入 一.创建动画控制器 二.创建动画 三.创建动画作用的组件 四.创建 AnimatedBuilder 关联动画与组件 五.动画运行 六.完整代码示例 ...
- 【Flutter】Animation 动画 ( AnimatedWidget 动画使用流程 | 创建动画控制器 | 创建动画 | 创建 AnimatedWidget 动画组件 | 动画运行 )
文章目录 ◯.AnimatedWidget 组件引入 一.创建 AnimatedWidget 动画组件 二.创建动画控制器 三.创建动画 四.动画运行 五.完整代码示例 六.相关资源 Animated ...
- 【Flutter】Animation 动画 ( Flutter 动画基本流程 | 创建动画控制器 | 创建动画 | 设置值监听器 | 设置状态监听器 | 布局中使用动画值 | 动画运行 )
文章目录 一.创建动画控制器 二.创建动画 三.设置值监听器 四.设置状态监听器 五.布局中使用动画值 六.动画运行 七.完整代码示例 八.相关资源 Flutter 动画基本流程 : ① 创建动画控制 ...
- 【Flutter】Animation 动画 ( Flutter 动画的核心类 | Animation | CurvedAnimation | AnimationController | Tween )
文章目录 一.动画的核心类 Animation 二.动画的核心类 CurvedAnimation 三.动画的核心类 AnimationController 四.动画的核心类 Tween 五.相关资源 ...
- 14-flutter Animation 动画
动画 一 Animation 在Flutter中,Animation对象本身和UI渲染没有任何关系.Animation是一个抽象类,它拥有其当前值和状态(完成或停止).其中一个比较常用的Animati ...
- flutter 透明度动画_Flutter中的动画填充+不透明度动画✨
flutter 透明度动画 Flutter SDK provides us with many widgets which help us in animating elements on scree ...
- Flutter Hero动画让你的APP页面切换充满动效 不一样的体验 不一样的细节处理
优美的应用体验 来自于细节的处理,更源自于码农的自我要求与努力,当然也需要码农年轻灵活的思维. 本文章实现的Demo效果,如下图所示: 1 首先是页面的主体 在这里使用的是Scaffold脚手架来构建 ...
- Flutter 平移动画 — 4种实现方式
系列文章 Flutter 旋转动画 - RotationTransition Flutter 平移动画 - 4种实现方式 Flutter 淡入淡出与逐渐出现动画 Flutter 尺寸缩放.形状.颜色. ...
- Flutter 自定义动画 — 数字递增动画和文字逐行逐字出现或消失动画
系列文章 Flutter 旋转动画 - RotationTransition Flutter 平移动画 - 4种实现方式 Flutter 淡入淡出与逐渐出现动画 Flutter 尺寸缩放.形状.颜色. ...
最新文章
- LeetCode简单题之最长的美好子字符串
- Eclipse中配置Tomcat
- 权限组件(4):给动态菜单增加面包屑导航
- C# 系统应用之鼠标模拟技术及自动操作鼠标
- 《数据库SQL实战》查找当前薪水详情以及部门编号dept_no
- 小程序 foreach_【第2106期】小程序依赖分析实践
- 解决:java.io.IOException: invalid constant type: 15
- android 65536 简书,app编译打包时的65536问题
- FastSpring.NET V2.05 final 发布[集成Spring.net NHibernate Ajax]
- mybatis开发中遇到的小问题
- 悬置线高通滤波器设计
- 如何用photoshop做24色环_photoshop制作漂亮色环的教程(2)
- PHP搭建留言板,用PHP制作留言板_php
- html match函数,match函数的使用方法 match函数怎么使用
- 2018年交通运行年报发布,深圳交通高质量发展取得新突破
- GLUE数据集免费下载 (MNLI, QQP, QNLI, SST-2, CoLA, STS-B, MRPC, RTE, WNLI)
- 机器学习(6)——凸优化理论(一)
- vue 实现点击插入输入框_vue把输入框的内容添加到页面的实例讲解
- cesium polygon添加边界线不起作用
- CSP第二轮比赛注意事项
热门文章
- 论文阅读:Reducing Transformer Depth On Demand With Structured Dropout
- zynq-7000系列基于zynq-7015的vivado初步设计之linux下控制PL扩展的光以太网(1000BASE-X)
- 第三代测序技术的兴起
- 6.0系统机器Xposed框架安装经验
- 为什么不要用System.out.println()
- EDI集成对业务的重要性
- 无奇不有,20款国外便携式智能手机充电器
- servlet工作流程
- 厦大计算机 专硕 分数线,厦门大学2020年专硕复试分数线
- 全连接神经网络的二分类问题