高效的动画系统作为Flutter的特性,不得不学习,接下来说下动画的分类,由浅入深,建议阅读两次,后面的知识会巩固前面的学习。 关注公众号:Flutter入门

动画的分类,可以分为隐式动画和显示动画,主要的区别是是否开发者自己控制AnimationController。
隐式动画(Implicit Animations):
AnimatedFoo
TweenAnimatedBuilder
显示动画(Explicit Animations):
FooTransaction
AnimatedWidget
AnimatedBuilder
隐式动画
AnimatedFoo
AnimatedFoo是Flutter framework提供的一系列的动画组件,动画改变的属性由名字就可以看出。继承自ImplicitlyAnimatedWidget统一管理AnimationController,不用开发者自己dispose()framework提供了:
AnimatedAlign
AnimatedContainer
AnimatedDefaultTextStyle
AnimatedOpacity
AnimatedPadding
AnimatedPhysicalMode
AnimatedPositioned
AnimatedPositionedDirectional
AnimatedTheme
AnimatedCrossFade
AnimatedSize
AnimatedSwitcher
动画效果可以看下心里有个印象。

使用方法:
AnimatedPadding(
duration: Duration(seconds: 1),
padding: EdgeInsets.all(_containerSelected ? 20 : 1),
curve: Curves.fastOutSlowIn,
child: Stack(
children: <Widget>[
FlutterLogo(size: 75),
Text("AnimatedPadding"),
],
),
)
可以看出只用传递动画时长和对应的动画变化值,就可以给child添加动效了。
需要注意的是有几个动画组件需要父组件必须是Stack;
AnimatedCrossFade更是需要父组件的宽高和AnimatedCrossFade一样,否则切换动画会有跳动,不过自己重写layoutBuilder这个方法就可以,可以详细看AnimatedImplicitWidget.dart中AnimatedCrossFade的具体使用。
AnimatedFoo继承自ImplicitlyAnimatedWidget,在改变属性的时候添加动画,首次添加到widget树的时候动画并不会执行;在重建时,如果属性值变了,会有动效的执行变化。
在动画执行过程中,每一帧都要执行build方法,在build方法中要构建每一帧的动画状态数据(即动画要改变的属性),通过evaluate(animation)获取每一帧对应的动画数据。
而开发者如果想自己实现ImplicitlyAnimatedWidget,只需要重写build和forEachTween分别获取每一帧的状态值和更新Tween的初始值。
拉官方的AnimatedPadding源码来看下,会有动效的改变padding值,State为_AnimatedPaddingState
class _AnimatedPaddingState extends AnimatedWidgetBaseState<AnimatedPadding> {
EdgeInsetsGeometryTween _padding;
//forEachTween初始化Tween值,其EdgeInsetsGeometryTween是自定义的Tween,生成两个EdgeInsetsGeometry之间的插值。
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
_padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value));
}
@override
Widget build(BuildContext context) {
return Padding(
padding: _padding.evaluate(animation),//通过动画生成对应的padding
child: widget.child,
);
}
}
//自定义Tween
class EdgeInsetsGeometryTween extends Tween<EdgeInsetsGeometry> {
EdgeInsetsGeometryTween({ EdgeInsetsGeometry begin, EdgeInsetsGeometry end }) : super(begin: begin, end: end);
@override
EdgeInsetsGeometry lerp(double t) => EdgeInsetsGeometry.lerp(begin, end, t);//此方法会根据时间变化,生成参数插值
}
TweenAnimatedBuilder
当Flutter framework没有实现对应的组件的动效的时候,可以考虑使用TweenAnimatedBuilder去控制任何组件的动效。
TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: _targetValue),
duration: Duration(seconds: 1),
builder: (BuildContext context, double value, Widget child) {
return Container(
width: value,
height: value,
child: child,
);
},
child: FlutterLogo(
size: 20,
),
)
builder对应的函数中,value是动画的Animation的值,即每一帧的动画值,可以更灵活的控制哪些组件的具体参数跟随者动画变化,对于framework没有实现的动画组件也可以实现。
显式动画
如果你想更多的控制动画的运行,比方说动画的开始、结束、重复等,以及动画中间的状态,就需要使用显示动画了;考虑使用AnimatedWidget或其子类,这样的话自己管理AnimationController类。
显式动画和隐式动画最主要的不同点是是否由开发者管理AnimationController,先说一下几个动画相关的概念:
Animation:一个抽象类,保存动画执行过程中的插值和状态,常用的子类有Animation,还有Animation、Animation根据自己需要改变的属性使用不同的泛型。
AnimationController:动画控制器,初始化动画的时长、返回值区间、启动、重复动画、监听状态等相关功能。初始化动画的时候需要一个vsync参数,是一个Ticker类型的子类,绑定Ticker的目的是当动画不在屏幕中的时不再执行动画,节省性能。因为应用启动的时候会绑定一个SchedulerBinding,通过SchedulerBinder在每次屏幕刷新的时候添加回调,Ticker是通过SchedulerBinder添加回调的,当锁屏时不再刷新屏幕,Ticker不会再触发,动画也就不再执行。
Curve:动画的曲线,动画执行过程中如果中间的值的变化非线性,可以使用Curve包装AnimationController将生成曲线动画,Curve和AnimationController都是Animation类型,正是通过此种方式将动画和动画曲线关联起来,因为这样可以直接使用Curve返回值作为动画运行的参数,实现非线性动画。常用CurvedAnimation添加动画曲线。
Tween:因为默认AnimationController返回值是0-1,当返回取值范围或类型不同时,使用Tween修饰动画。值得注意的是Tween继承自Animatable。Tween提供了animation()方法,传入Animation,返回Animation,来使用返回的值控制组件的显示。
使用动画的步骤可以大体分为(并非所有步骤必须):
初始化AnimationController->[将AnimationController传入Curve,添加动画曲线]->[将AnimaitionController或Curve传入Tween生成需要的插值]->AnimationController或Curve或Tween生成的插值给到组件的参数,实现动画

如图中显式动画的组件,Curve、Tween都是根据需要使用。
FooTransaction
FooTransition指的是一系列组件,这些组件继承自AnimatedWidget,会根据给定的Listenable(AnimationController)变化的时候,重新构建。framework提供了有
AlignTransition
DecoratedBoxTransition
DefaultTextStyleTransition
PositionedTransition
RelativePositionedTransition
RotationTransition
ScaleTransition
SizeTransition
SlideTransition
FadeTransition
AnimatedModalBarrier

下面看一个例子:
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
)..repeat(reverse: true);
_rotationDoubleAnimation = Tween<double>(
begin: 0.0,
end: 0.05,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOutSine,
),
);
}
//使用animation,省略了部分代码...
RotationTransition(
turns: _rotationDoubleAnimation,
child: _ViewWidget(
showText: 'RotationTransition',
),
)
可以看到开发者自己初始化AnimationController,可以通过AnimationController控制、监听动画的状态。
需要提醒的是,并不是所有的动效参数通过Tween传递给对应的FooTransition就可以,例如PositionedTransition的rect参数需要Tween参数,这时候不能直接传递Tween过去,而是要传递封装的RelativeRectTween,RelativeRectTween继承自Tween有复写lerp方法,这个方法是子类自定义动画行为的一个方法。对于父类Tween lerp方法只有begin + (end - begin) * t,对于复杂的参数(例如:RelativeRect),参数没有重写运算符(+、-),要自己复写对应参数的运算符方法,或者继承Tween重写lerp,所以使用官方对应的FooTween更为便捷(如果有实现的话)。
AnimatedWidget
AnimatedWidget是一个抽象类,需要自己继承这个类,传递一个显示的Listenable(实现类包括Animation,ChangeNotifier,ValueNotifier等)作为其参数,通常是AnimationController驱动的Animation,AnimationController由开发者管理,对于FooTransition没有实现的动画效果,可以自己使用AnimatedWidget封装。
class _ExplicitAnimationState extends State<_ExplicitAnimationPage> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
@override
void initState() {
super.initState();
//初始化动画控制器
controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
//因为动画控制器默认返回0-1之间的值,所以需要将动画的值转成需要的区间或类型
animation = Tween<double>(begin: 10, end: 100).animate(controller);
//添加动画控制器动画相关的属性
controller.repeat(reverse: false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
//使用自定义的AnimatedWidget子类
child: _AnimatedFlutterLogo(animation: animation),
),
);
}
}
//实现AnimatedWidget子类
class _AnimatedFlutterLogo extends AnimatedWidget {
final Animation<double> animation;
_AnimatedFlutterLogo({Key key, @required this.animation}) : super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
return Container(
//接收外界传递过来的animation,使用动画的值控制组件宽高
width: animation.value,
height: animation.value,
child: FlutterLogo(),
);
}
}

动画需要变化更复杂一点
class _CurveAnimatedFlutterLogo extends AnimatedWidget {
final Animation<double> controller;
_CurveAnimatedFlutterLogo({Key key, @required this.controller}) : super(key: key, listenable: controller);
//控制组件的透明度
final _opacityTween = Tween<double>(begin: 0.1, end: 1);
//控制组件的大小
final _sizeTween = Tween<double>(begin: 10, end: 200);
@override
Widget build(BuildContext context) {
return Opacity(
//直接evaluate(controller)即可获得对应的动画值
opacity: _opacityTween.evaluate(controller),
child: Container(
//也可通过Tween.animate获得Animation<T>,再取对应的值
width: _sizeTween.animate(controller).value,
height: _sizeTween.evaluate(controller),
child: FlutterLogo(),
),
);
}
}
这个传AnimationController进来,处理不同属性的变化。

AnimatedBuilder:
class _AnimatedBuilderWidget extends StatelessWidget {
_AnimatedBuilderWidget({Key key, this.child, this.animation}) : super(key: key);
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
child: child,
builder: (context, child) => Container(
height: animation.value,
width: animation.value,
child: child,
));
}
}
动画效果就不在额外的贴出来了。AnimatedBuilder作为自定义性更好的组件,优势是性能更好,对于只想把动画作为一部分的复杂的组件,AnimationBuilder是一个不错的选择,如果builder包括不依赖动画的子组件,这个子组件不会每次都重建,优化了性能。
怎么选择:
对于需要额外的state的复杂组件的情况,考虑使用AnimatedBuilder,AnimatedBuilder性能更好。
AnimatedWidget和其子类,传递一个显示的Listenable(实现类包括Animation,ChangeNotifier,ValueNotifier等)作为其参数,通常是AnimationController驱动的Animation,AnimationController由开发者管理。
而ImplicitlyAnimatedWidget自动管理内部的AnimationController,更方便开发,如果只是设置一个目标值给动画,配置时间和曲线,考虑使用ImplicitlyAnimatedWidget或其子类,Flutter framework提供了一系列动画组件,FooTransition,这些动画和ImplicitlyAnimatedWidget子类不同,其子类通常是AnimatedFoo。
原则是能使用Flutter提供的就使用Flutter提供的,能使用Flutter管理AnimationController的就是有Flutter管理的隐式动画。实在比较复杂的时候再使用AnimatedWidget或AnimatedBuilder。