Animation
是一个抽象类,它由一个值(类型为T)和一个状态组成。 状态指示动画在概念上是从开始到结束或从结束到开始,尽管动画的实际值可能不会单调更改(例如,如果动画使用反弹的曲线bounces curve
)。
查看源码,可以在里面看到一些方法,如相关的监听 Listener
,获取动画值的方法value
。
Animation
的使用需要配合AnimationController
,所以我们先介绍完AnimationController
再实现示例。
动画控制器,它包含动画的启动
forward()
、停止stop()
、反向播放reverse()
等方法。AnimationController
需要一个TickerProvider
,它使用构造函数上的vsync
参数配置。TickerProvider
接口是描述Ticker
对象的工厂。Ticker
是一个对象,它知道如何向SchedulerBinding
注册,并在每一帧触发回调。AnimationController
类使用Ticker
逐步控制动画,而AnimationController
中的必要参数TickerProvider vsync
,可以使用TickerProvider 和 SingleTickerProviderStateMixin
,但通常我们会将SingleTickerProviderStateMixin(效率稍高一些)
添加到State
的定义中,然后将State
对象作为vsync
的值,详情可查看下面的示例。
实现居中图片重复执行的线性动画。
实现上面动图中的效果只需要以下代码即可完成:
class _AnimationTestPageState extends State
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
@override
void initState() {
super.initState();
// 创建 AnimationController 对象
controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 1000));
// 创建线性变化的 Animation 对象
animation = Tween(begin: 10.0, end: 100.0).animate(controller)
..addListener(() {
setState(() {}); // 刷新界面,必须要有,否则动画动不了
});
//让动画重复执行
controller.repeat(reverse: true);
}
@override
void dispose() {
controller.dispose(); // 释放资源
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimationTest"),
),
body: SingleChildScrollView(
child: Column(
children: [
Center(
child: Container(
width: animation.value,// 图片宽高使用动画的值
height: animation.value,
child: Image.network(
'https://hbimg.huabanimg.com/fced2db29a9354db4747822b819b247d88adbb9be837-bB3TR0_fw658'),
)),
)
],
),
));
}
}
AnimatedWidget
会将Animation
的状态与其子widget
的视觉样式绑定。省去了状态监听和 UI 刷新
的工作。
也就是使用 AnimatedWidget
来实现动画,就可以不用写下面这段代码了。
..addListener(() {
setState(() {}); // 刷新界面,必须要有,否则动画动不了
});
使用过程也比较简单,首先创建一个widget
继承AnimatedWidget
,在widget
中传递动画的值及动画的在哪个child
上显示
/// 使用 AnimatedWidget 创建动画
class AnimatedFlutterLogo extends AnimatedWidget {
// AnimatedWidget 需要在初始化时传入animation对象,所以构造函数不能少
AnimatedFlutterLogo({Key key, Animation animation}) : super(key: key, listenable: animation);
Widget build(BuildContext context) {
// 取出动画对象
final Animation animation = listenable;
return Center(
child: Container(
height: animation.value, //根据动画对象的当前状态更新宽高
width: animation.value,
child: FlutterLogo()));// FlutterLogo 是系统系统的 widget
}
}
创建好widget
之后,在创建animation
的时候就不用监听动画和刷新动画了
代码如下:
class _AnimationTestPageState extends State
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
@override
void initState() {
super.initState();
// 创建 AnimationController 对象
controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 1000));
// 创建线性变化的 Animation 对象
animation = Tween(begin: 10.0, end: 100.0).animate(controller);
//让动画重复执行
controller.repeat(reverse: true);
}
@override
void dispose() {
controller.dispose(); // 释放资源
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimationTest"),
),
body: SingleChildScrollView(
child: Column(
children: [
Center(
child: AnimatedFlutterLogo(
animation: animation) // 初始化 AnimatedWidget 时传入animation对象
),
],
),
));
}
}
从AnimatedWidget
中可以看出widget
动画的控制和widget
本身是存在耦合的。
能不能把图中的耦合去除呢?
Flutter
中提供了AnimatedBuilder
来实现动画,使用AnimatedBuilder
实现动画可以将widget
和动画的控制解耦。
使用
AnimatedBuilder
实现动画可以将widget
和动画的控制解耦。
我们可以创建一个widget
封装AnimatedBuilder
,参数通过构造参数方法传入。
class AnimatedFlutterLogoBuilder extends StatelessWidget{
final Widget child;
final AnimationController controller;
final Animation animation;
AnimatedFlutterLogoBuilder({this.child,this.animation});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation, // 传入动画对象
child: child,
// 动画构建回调
builder: (context, child) => Container(
width: animation.value, //使用动画的当前状态更新UI
height: animation.value,
child: child, // 即 AnimatedBuilder 中的 child
),);
}
}
有了widget
之后,直接调用并传入相关参数即可:
Center(
child: AnimatedFlutterLogoBuilder(
child: Image.network(
'https://hbimg.huabanimg.com/fced2db29a9354db4747822b819b247d88adbb9be837-bB3TR0_fw658'),
animation: animation,
))
可能从上面的示例无法看出二者的区别,那么我们通过实现以下的效果来区分二者的差别
上面的第二个效果图,如果用AnimationWidget
来实现需要定义两个AnimationWidget
,分别是:
/// 使用 AnimatedWidget 创建动画,传入 Animation
class AnimatedFlutterLogo extends AnimatedWidget {
// AnimatedWidget 需要在初始化时传入animation对象,所以构造函数不能少
AnimatedFlutterLogo({Key key, Animation animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
// 取出动画对象
final Animation animation = listenable;
return Center(
child: Container(
height: animation.value, //根据动画对象的当前状态更新宽高
width: animation.value,
child: Image.network(
'https://hbimg.huabanimg.com/fced2db29a9354db4747822b819b247d88adbb9be837-bB3TR0_fw658'),
),
);
}
}
/// 使用 AnimatedWidget 创建动画,传入 AnimationController
class AnimatedFadeFlutterLogo extends AnimatedWidget {
// AnimatedWidget 需要在初始化时传入animation对象,所以构造函数不能少
AnimatedFadeFlutterLogo({Key key, AnimationController controller})
: super(key: key, listenable: controller);
Widget build(BuildContext context) {
// 取出动画对象
final AnimationController controller = listenable;
// 创建线性变化的 Animation 对象
var animation = Tween(begin: 10.0, end: 100.0).animate(controller);// 通过 controller 生成 animation
// 透明度动画
return Center(
child: FadeTransition(
opacity: controller,
child: AnimatedFlutterLogo(animation: animation,),// 使用第一个 AnimatedWidget,并传入 animation
),
);
}
}
定义好两个widget
之后,就可以直接使用AnimatedFadeFlutterLogo
这个类做child
了,使用如下:
Center(
child: AnimatedFadeFlutterLogo(
controller: controller) // 初始化 AnimatedWidget 时传入animation对象
),
还是定义widget
,并在构造方法传入需要的参数:
class AnimatedFlutterLogoBuilder extends StatelessWidget{
final Widget child;
final AnimationController controller;
final Animation animation;
AnimatedFlutterLogoBuilder({this.child,this.controller,this.animation});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation, // 传入动画对象
child: child,
// 动画构建回调
builder: (context, child) => FadeTransition(// 透明度动画
opacity: controller,
child: Container(
width: animation.value, //使用动画的当前状态更新UI
height: animation.value,
child: child, // 即 AnimatedBuilder 中的 child
),
));
}
}
使用定义好的widget
Center(
child: AnimatedFlutterLogoBuilder(
child: Image.network(
'https://hbimg.huabanimg.com/fced2db29a9354db4747822b819b247d88adbb9be837-bB3TR0_fw658'),
controller: controller,
animation: animation,
))
由于继承自AnimationWidget
的类构造方法每次只能传递两个参数,这就导致当需要传递多个参数时比较麻烦。而使用AnimatedBuilder
则不会受参数个数的限制。而且使用AnimatedBuilder
相当于是将动画的实现封装出去,而使用的时候只管传递child widget
和相关参数即可。