Flutter 动画库的核心类是 Animation 对象,它生成指导动画的值,Animation 对象指导动画的当前状态(例如,是开始、停止还是向前或者向后移动),但它不知道屏幕上显示的内容。动画类型分为两类:
- 补简动画(Tween),定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过渡到结束点。Tween是一个无状态(stateless)对象,需要begin和end值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的。
- 基于物理动画,运动被模拟与真实世界行为相似,例如,当你掷球时,它何处落地,取决于抛球速度有多快、球有多重、距离地面有多远。类似地,将连接在弹簧上的球落下(并弹起)与连接到绳子的球放下的方式也是不同。
在Flutter中的动画系统基于Animation对象的。widget可以在build函数中读取Animation对象的当前值,并且可以监听动画的状态改变。
1. 动画示例
1. 代码
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: 3000
),
//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()
方法中调用释放资源
2. 效果图
3. 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: 3000
),
//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
宽高。
4. 监视动画
在平时开发,我们知道,很多时候都需要监听动画的状态,好像完成、前进、倒退等。在Flutter中可以通过addStatusListener()
来得到这个通知,以下代码添加了动画状态
//补间动画
animation = new Tween(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 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(
//开始的值是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();
}
});//添加监听器
效果如下:
5. 用 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: 3000
),
//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
。
6. 并行动画
很多时候,一个动画需要两种或者两种以上的动画,在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);
@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: 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();
}
@override
Widget build(BuildContext context) {
return new GrowTransition(child:ImageLogo,animation: animation);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
//资源释放
controller.dispose();
}
}
可以看到在GrowTransition
定义两个Tween
动画,并且加了不透明Opacitywidget
,最后在initState
方法中修改增加一句animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)
,最后的动画效果:
2. 自定义动画
1. 效果图
2. 自定义小球
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 - 15) / 8;
Paint paint = new Paint();
paint.color = color;
paint.style = PaintingStyle.fill;
//因为这个wiaget是80 球和球之间相隔5
for(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);
}
}
//刷新是否重绘
@override
bool shouldRepaint(CustomPainter oldDelegate){
return oldDelegate != this;
}
}
3. 配置小球属性
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});
@override
State<StatefulWidget> createState(){
return MyBallsState();
}
}
4. 创建动画
//继承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 = 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;
});
}
}
@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[0].removeListener((){
setState(() {
});
});
animationControllers[0].dispose();
}
}
5. 调用
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)),
),
),
);
}
}