【Flutter学习】之动画实现原理浅析(三)
一,概述
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{
@override
State<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;
@override
void initState() {
// TODO: implement initState
super.initState();
//创建AnimationController
//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
controller = new AnimationController(
//时间是3000毫秒
duration: const Duration(
milliseconds:
),
//vsync 在此处忽略不必要的情况
vsync: this,
);
//补间动画
animation = new Tween(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 200.0,
).animate(controller)//添加监听器
..addListener((){
//动画值在发生变化时就会调用
setState(() { });
});
//只显示动画一次
controller.forward();
}
@override
Widget 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,
),
),
),
);
} @override
void dispose() {
// TODO: implement dispose
super.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{
@override
State<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); @override
Widget 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;
@override
void initState() {
// TODO: implement initState
super.initState();
//创建AnimationController
//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
controller = new AnimationController(
//时间是3000毫秒
duration: const Duration(
milliseconds:
),
//vsync 在此处忽略不必要的情况
vsync: this,
);
//补间动画
animation = new Tween(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 200.0,
).animate(controller);//添加监听器
//只显示动画一次
controller.forward();
} @override
Widget build(BuildContext context){
return AnimatedLogo(animation: animation);
} @override
void dispose() {
// TODO: implement dispose
super.dispose();
//资源释放
controller.dispose();
}
}可以发现
AnimatedWidget
中会自动调用addListener
和setState()
,_LogoAppState
将Animation
对象传递给基类并用animation.value
设置Image宽高。1.2.监视动画
在平时开发,我们知道,很多时候都需要监听动画的状态,好像完成、前进、倒退等。在
Flutter
中可以通过addStatusListener()
来得到这个通知,以下代码添加了动画状态//补间动画
animation = new Tween(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 200.0,
).animate(controller)
//添加动画状态
..addStatusListener((state){
return print('$state');
});//添加监听器运行代码会输出下面结果:
I/flutter (): AnimationStatus.forward //动画开始
Syncing files to device KNT AL10...
I/zygote64(): Do partial code cache collection, code=30KB, data=25KB
I/zygote64(): After code cache collection, code=30KB, data=25KB
I/zygote64(): Increasing code cache capacity to 128KB
I/flutter (): AnimationStatus.completed//动画完成下面那就运用
addStatusListener()
在开始或结束反转动画。那就产生循环效果://补间动画
animation = new Tween(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 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}); @override
Widget 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;
@override
void initState() {
// TODO: implement initState
super.initState();
//创建AnimationController
//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
controller = new AnimationController(
//时间是3000毫秒
duration: const Duration(
milliseconds:
),
//vsync 在此处忽略不必要的情况
vsync: this,
);
final CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
//补间动画
animation = new Tween(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 200.0,
).animate(curve)
// //添加动画状态
..addStatusListener((state){
//如果动画完成了
if(state == AnimationStatus.completed){
//开始反向这动画
controller.reverse();
} else if(state == AnimationStatus.dismissed){
//开始向前运行着动画
controller.forward();
}
});//添加监听器
//只显示动画一次
controller.forward();
} @override
Widget build(BuildContext context){
//return AnimatedLogo(animation: animation);
return new GrowTransition(child:ImageLogo,animation: animation);
} @override
void dispose() {
// TODO: implement dispose
super.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: ), 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); @override
Widget 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; @override
void initState() {
// TODO: implement initState
super.initState();
//创建AnimationController
//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
controller = new AnimationController(
//时间是3000毫秒
duration: const Duration(milliseconds: ),
//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();
} @override
Widget build(BuildContext context) {
return new GrowTransition(child:ImageLogo,animation: animation);
} @override
void dispose() {
// TODO: implement dispose
super.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});
@override
void paint(Canvas canvas,Size size){
//绘制流程
double boll_radius = (size.width - ) / ;
Paint paint = new Paint();
paint.color = color;
paint.style = PaintingStyle.fill;
//因为这个wiaget是80 球和球之间相隔5
for(int i = ; i < count;i++){
double value = ListAnimators[i].value;
//确定圆心 半径 画笔
//第一个球 r
//第二个球 5 + 3r
//第三个球 15 + 5r
//第四个球 30 + 7r
//半径也是随着动画值改变
canvas.drawCircle(new Offset((i+) * boll_radius + i * boll_radius + i * ,size.height / ), boll_radius * (value > ? ( - value) : value), paint);
}
} //刷新是否重绘
@override
bool 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 : ,this.color :Colors.redAccent,this.count : });
@override
State<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; @override
void initState(){
super.initState();
for(int i = ;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[].addListener((){
//刷新
setState(() {
});
}); //延迟执行
var delay = (widget.seconds ~/ ( * animatios.length - ));
for(int i = ;i < animatios.length;i++){
Future.delayed(Duration(milliseconds: delay * i),(){
animationControllers[i]
..repeat().orCancel;
});
}
}
@override
Widget build(BuildContext context){
return new CustomPaint(
//自定义画笔
painter: _bollView(color: colors.value,count: widget.count,ListAnimators : animatios),
size: widget.size,
);
}
//释放资源
@override
void dispose(){
super.dispose();
animatios[].removeListener((){
setState(() {
});
});
animationControllers[].dispose();
}
}2.4.调用
class Ball extends StatelessWidget{
@override
Widget 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学习】之动画实现原理浅析(三)的更多相关文章
- 【Flutter学习】之动画实现原理浅析(一)
一,动画介绍 动画对于App来说,非常的重要.很多App,正是因为有了动画,所以才会觉得炫酷.移动端的动画库有非常的多,例如iOS上的Pop.web端的animate.css.Android端的And ...
- 【Flutter学习】之动画实现原理浅析(二)
1. 介绍 本文会从代码层面去介绍Flutter动画,因此不会涉及到Flutter动画的具体使用. 1.1 Animation库 Flutter的animation库只依赖两个库,Dart库以及phy ...
- Dubbo学习(一) Dubbo原理浅析
一.初入Dubbo Dubbo学习文档: http://dubbo.incubator.apache.org/books/dubbo-user-book/ http://dubbo.incubator ...
- Flutter学习笔记(36)--常用内置动画
如需转载,请注明出处:Flutter学习笔记(36)--常用内置动画 Flutter给我们提供了很多而且很好用的内置动画,这些动画仅仅需要简单的几行代码就可以实现一些不错的效果,Flutter的动画分 ...
- Flutter学习笔记(37)--动画曲线Curves 效果
如需转载,请注明出处:Flutter学习笔记(37)--动画曲线Curves 效果
- 【Flutter学习一】Android的App的三种开发方式
是时候学习新技术了: 转自:https://blog.csdn.net/qq_41346910/article/details/86692124 移动开发发展到现在,已经出现了三种开发方式.本文我将为 ...
- 脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手
.引言 网络编程中TCP协议的三次握手和四次挥手的问题,在面试中是最为常见的知识点之一.很多读者都知道“三次”和“四次”,但是如果问深入一点,他们往往都无法作出准确回答. 本篇文章尝试使用动画图片的方 ...
- [转帖]脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手
脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手 http://www.52im.net/thread-1729-1-1.html 1.引言 网络编程中TCP协议的三次握手和 ...
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8611754 在前一文中,我们分析了Activi ...
随机推荐
- pyCharm和解释器下载安装
参考:(mac) 安装流程和注意: http://blog.csdn.net/limin2928/article/details/69267184 解释器下载地址: https://www.pytho ...
- exists 的简单介绍
准备数据: CREATE TABLE Books( BookID number, BookTitle VARCHAR2(20) NOT NULL, Copyright varchar2(20) ) I ...
- 洛谷 P3806 (点分治)
题目:https://www.luogu.org/problem/P3806 题意:一棵树,下面有q个询问,问是否有距离为k的点对 思路:牵扯到树上路径的题都是一般都是点分治,我们可以算出所有的路径长 ...
- [CSP-S模拟测试]:Read(数学)
题目描述 热爱看书的你有$N$本书,第$i$本书的种类为$A[i]$.你希望每天能够看一本书,但是不希望连续两天看种类相同的书.为了达成这个条件,你需要选择一些书不看,作为一个好学生,你希望不看的书尽 ...
- CentOS7下python虚拟环境
搭建python虚拟环境 1.我们先创建一个隐藏目录 .virtualenvs,所有的虚拟环境都放在此目录下 :mkdir /root/.virtualenvs 2.安装虚拟环境 确认pip:wher ...
- OpenCV2.4.8 + CUDA7.5 + VS2013 配置
配置过程主要参考:https://initialneil.wordpress.com/2014/09/25/opencv-2-4-9-cuda-6-5-visual-studio-2013/ 1.为什 ...
- boostrap中lg,md,sm,xs分别对应的像素宽度
col-xs- 超小屏幕 手机 (<768px)col-sm- 小屏幕 平板 (≥768px)col-md- 中等屏幕 桌面显示器 (≥992px)col-lg- 大屏幕 大桌面显 ...
- 插件化框架解读之四大组件调用原理-Service(三)下篇
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本文将继续通过Service调用原理来解读Replugin插件化 ...
- [fw]IDT表的初始化
IDT表的初始化 linux内核的中断描述符表IDT是一个全局的数据,在i386平台上被定义为: struct desc_struct idt_table[256] __attribute__((_ ...
- Charles 抓 HTTPS 包
最新 Charles 破解版下载地址:http://charles.iiilab.com/ 关掉翻墙软件!!!!! 重启 Charles !!!!! 重启浏览器!!!!! 如果是抓手机的HTTPS包, ...