如何使用动画库中的基本类为 widget 添加动画。javascript
AnimatedWidget
和 AnimatedBuilder
的应用区别。java
本教程将讲解如何在 Flutter 中构建显式动画。咱们先来介绍一些动画库中的基本概念,类和方法,而后列举五个动画示例。这些示例互相关联,展现了动画库的不一样方面。git
Flutter SDK 也提供过渡动画,好比 FadeTransition,SizeTransition 和 SlideTransition。这些简单的动画能够经过设置起点和终点来触发。它们比下面介绍的显式动画更容易实现。github
1. 基本动画概念和类闭包
Animation
,Flutter 动画库中的核心类,插入用于指导动画的值。app
Animation
对象知道动画目前的状态(例如,是否开始,暂停,前进或倒退),可是对屏幕上显示的内容一无所知。less
AnimationController
管理 Animation
。ide
CurvedAnimation
定义进程为非线性曲线。函数
Tween
为动画对象插入一个范围值。例如,Tween
能够定义插入值由红到蓝,或从 0 到 255。学习
使用 Listeners 和 StatusListeners 监视动画状态变化。
Flutter 中的动画系统基于类型化的 Animation 对象。Widgets 既能够经过读取当前值和监听状态变化直接合并动画到 build 函数,也能够做为传递给其余 widgets 的更精细动画的基础。
1.1 Animation<double>
在 Flutter 中,动画对象没法获取屏幕上显示的内容。Animation
是一个已知当前值和状态(已完成或已解除)的抽象类。一个比较常见的动画类型是 Animation<double>
。
一个 Animation
对象在一段时间内,持续生成介于两个值之间的插入值。这个 Animation
对象输出的多是直线,曲线,阶梯函数,或者任何自定义的映射。根据 Animation
对象的不一样控制方式,它能够反向运行,或者中途切换方向。
动画还能够插入除 double 之外的类型,好比 Animation<Color>
或者 Animation<Size>
。
Animation
对象具备状态。它的当前值在 .value
中始终可用。
Animation
对象与渲染或 build()
函数无关。
1.2 CurvedAnimation
CurvedAnimation 定义动画进程为非线性曲线。
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
import 'dart:math'; class ShakeCurve extends Curve { @override double transform(double t) => sin(t * pi * 2); }
CurvedAnimation
和
AnimationController
(下面将会详细说明)都是
Animation<double>
类型,因此能够互换使用。
CurvedAnimation
封装正在修改的对象 — 不须要将
AnimationController
分解成子类来实现曲线。
AnimationController
是个特殊的
Animation
对象,每当硬件准备新帧时,他都会生成一个新值。默认状况下,
AnimationController
在给按期间内会线性生成从 0.0 到 1.0 的数字。例如,这段代码建立了一个动画对象,可是没有启动运行。
controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);
AnimationController
源自于
Animation<double>
,因此能够用在任何须要
Animation
对象的地方。可是
AnimationController
还有其余方法控制动画。例如,使用
.forward()
方法启动动画。数字的生成与屏幕刷新关联,因此通常来讲每秒钟会生成 60 个数字。数字生成以后,每一个动画对象都调用附加 Listener 对象。为每一个 child 建立自定义显示列表,请参考
RepaintBoundary
。
AnimationController
的同时,也赋予了一个
vsync
参数。
vsync
的存在防止后台动画消耗没必要要的资源。您能够经过添加
SingleTickerProviderStateMixin
到类定义,将有状态的对象用做 vsync。可参考 GitHub 网站 animate1 中的示例。
AnimationController
的 0.0-1.0 的范围。例如,
fling()
函数容许提供速度,力和位置(经过 Force 对象)。这个位置能够是任意的,因此可能会超出 0.0-1.0 的范围。
AnimationController
在范围内,
CurvedAnimation
也可能会出现超出 0.0-1.0 范围的状况。根据所选曲线的不一样,
CurvedAnimation
的输出范围可能会超过输入。举个例子,弹性曲线(好比Curves.elasticIn)会明显超出或低于默认范围。
AnimationController
对象的范围是 0.0-0.1。若是须要不一样的范围或者不一样的数据类型,可使用
Tween
配置动画来插入不一样的范围或数据类型。例以下面的示例中,
Tween
的范围是 -200 到 0.0。
tween = Tween<double>(begin: -200, end: 0);
Tween
是无状态的对象,只有
begin
和
end
。
Tween
的这种单一用途用来定义从输入范围到输出范围的映射。输入范围通常为 0.0-1.0,但这并非必须的。
Tween
源自
Animatable<T>
,而不是
Animation<T>
。像动画这样的可动画元素没必要重复输出。例如,
ColorTween
指定了两种颜色之间的过程。
colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);
Tween
对象不存储任何状态。而是提供
evaluate(Animation<double> animation)
方法,将映射函数应用于动画当前值。
Animation
对象的当前值能够在
.value
方法中找到。evaluate 函数还执行一些内部处理内容,好比确保当动画值在 0.0 和1.0 时分别返回起始点和终点。
1.4.1 Tween.animate
Tween
对象,请在
Tween
调用
animate()
,传入控制器对象。例如,下面的代码在 500 ms 的进程中生成 0-255 范围内的整数值。
AnimationController controller = AnimationController( duration: const Duration(milliseconds: 500), vsync: this); Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);
animate()
方法会返回一个
Animation
,而不是
Animatable
。
Tween
。
AnimationController controller = AnimationController( duration: const Duration(milliseconds: 500), vsync: this); final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut); Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);
Animation
对象能够有不止一个
Listener
和
StatusListener
,用
addListener()
和
addStatusListener()
来定义。当动画值改变时调用
Listener
。
Listener
最经常使用的操做是调用
setState()
进行重建。当一个动画开始,结束,前进或后退时,会调用
StatusListener
,用
AnimationStatus
来定义。下一部分有关于
addListener()
方法的示例,在 2.3章节
监控动画过程
中也有
addStatusListener()
的示例。
addListener()
和
setState()
为 widget 添加基础动画。
addListener()
函数就会调用
setState()
。
AnimatedController
with the required
vsync
parameter. 如何使用所需的
vsync
参数定义一个
AnimatedController
。
..addListener
” 中的 “
..
” 语法,也称为 Dart 的 cascade notation。
_
)。
Animation
对象,需将
Animation
对象存储为您的 widget 成员,而后用它的值来决定如何绘制。
import 'package:flutter/material.dart'; void main() => runApp(LogoApp()); class LogoApp extends StatefulWidget { _LogoAppState createState() => _LogoAppState(); } class _LogoAppState extends State<LogoApp> { @override Widget build(BuildContext context) { return Center( child: Container( margin: EdgeInsets.symmetric(vertical: 10), height: 300, width: 300, child: FlutterLogo(), ), ); } }
源代码 animate0:
https://github.com/cfug/flutter.cn/tree/master/examples/animation/animate0
AnimationController
时,必需要使用一个
vsync
对象。在
AnimationController
部分会具体介绍
vsync
参数。
源代码 animate1:
addListener()
函数调用
setState()
,因此每次
Animation
生成一个新的数字,当前帧就被标记为 dirty,使得
build()
再次被调用。在
build()
函数中,container 会改变大小,由于它的高和宽都读取
animation.value
,而不是固定编码值。当动画结束时要清除控制器以防止内存溢出。
..addListener()
中的两点)不太熟悉。这个语法意思是使用
animate()
的返回值调用
addListener()
方法。参考下面示例:
animation = Tween<double>(begin: 0, end: 300).animate(controller) ..addListener(() { // ··· });
animation = Tween<double>(begin: 0, end: 300).animate(controller); animation.addListener(() { // ··· });
AnimatedWidget
帮助类(代替
addListener()
和
setState()
)建立动画
widget
。
AnimatedWidget
建立一个能够运行重复使用动画的 widget。如需区分 widget 过渡,可使用
AnimatedBuilder
。
AnimatedWidget
:AnimatedBuilder, AnimatedModalBarrier, DecoratedBoxTransition, FadeTransition, PositionedTransition, RelativePositionedTransition, RotationTransition, ScaleTransition, SizeTransition, SlideTransition。
AnimatedWidget
基本类能够从动画代码中区分出核心 widget 代码。
AnimatedWidget
不须要保持
State
对象来 hold 动画。能够添加下面的
AnimatedLogo
类:
class AnimatedLogo extends AnimatedWidget { AnimatedLogo({Key key, Animation<double> animation}) : super(key: key, listenable: animation); Widget build(BuildContext context) { final Animation<double> animation = listenable; return Center( child: Container( margin: EdgeInsets.symmetric(vertical: 10), height: animation.value, width: animation.value, child: FlutterLogo(), ), ); } }
在绘制时,AnimatedLogo
会读取 animation
当前值。
LogoApp
持续控制
AnimationController
和
Tween
,并将
Animation
对象传给
AnimatedLogo
:
addStatusListener()
做为动画状态的变动提示,好比开始,结束,或改变方向。
addStatusListener()
来得到提示。下面是以前示例修改后的代码,这样就能够监听状态的改变和更新:
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin { Animation<double> animation; AnimationController controller; @override void initState() { super.initState(); controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); animation = Tween<double>(begin: 0, end: 300).animate(controller) ..addStatusListener((state) => print('$state')); controller.forward(); } // ... }
AnimationStatus.forward AnimationStatus.completed
下一步,在起始或结束时,使用 addStatusListener()
反转动画。制造“呼吸”效果:
源代码 animate3:
https://github.com/cfug/flutter.cn/tree/master/examples/animation/animate3
2.4 使用 AnimatedBuilder 进行重构
AnimatedBuilder
知道如何渲染过渡效果
AnimatedBuilder
不会渲染 widget,也不会控制动画对象。
AnimatedBuilder
描述一个动画是其余 widget 构建方法的一部分。
若是只是单纯须要用可重复使用的动画定义一个 widget,可参考
AnimatedWidget.
。
AnimatedBuilders
:
BottomSheet
,
ExpansionTile
,
PopupMenu
,
ProgressIndicator
,
RefreshIndicator
,
Scaffold
,
SnackBar
,
TabBar
,
TextField
。
animate3
示例代码中有个问题,就是改变更画须要改变渲染 logo 的widget。
较好的解决办法是,将任务区分到不一样类里:
AnimatedBuilder
类方法来完成分配。
AnimatedBuilder
做为渲染树的一个单独类。
像
AnimatedWidget
,
AnimatedBuilder
自动监听动画对象提示,并在必要时在 widget 树中标出,因此这时不须要调用
addListener()
。
class LogoWidget extends StatelessWidget { // Leave out the height and width so it fills the animating parent Widget build(BuildContext context) => Container( margin: EdgeInsets.symmetric(vertical: 10), child: FlutterLogo(), ); }
GrowTransition
中的
build()
方法建立的,以下。
GrowTransition
widget 自己是无状态的,并且拥有定义过渡动画所需的一系列最终变量。
build() 函数建立并返回
AnimatedBuilder
,
AnimatedBuilder
使用(
Anonymous
builder)方法并将 LogoWidget 对象做为参数。
渲染过渡效果其实是在(
Anonymous
builder)方法中完成的,该方法建立一个适当大小
Container
强制
LogoWidget
配合。
AnimatedBuilder
,再传递给匿名闭包,而后用做 child 的对象。
最终结果就是
AnimatedBuilder
被插入渲染树的两个 widgets 中间。
class GrowTransition extends StatelessWidget { GrowTransition({this.child, this.animation}); final Widget child; final Animation<double> animation; Widget build(BuildContext context) => Center( child: AnimatedBuilder( animation: animation, builder: (context, child) => Container( height: animation.value, width: animation.value, child: child, ), child: child), ); }
animate2
的示例。
initState()
方法建立了
AnimationController
和
Tween
,而后用
animate()
绑定它们。
神奇的是
build()
方法,它返回一个以
LogoWidget
为 child 的
GrowTransition
对象,和一个驱动过渡的动画对象。
上面列出了三个主要因素。
AnimatedWidget
持续进行动画。
能够用在须要对透明度进行从透明到不透明动画处理的状况。
controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); sizeAnimation = Tween<double>(begin: 0, end: 300).animate(controller); opacityAnimation = Tween<double>(begin: 0.1, end: 1).animate(controller);
经过 sizeAnimation.value
咱们能够获得尺寸,经过 opacityAnimation.value
能够获得不透明度,可是 AnimatedWidget
的构造函数只读取单一的 Animation
对象。为了解决这个问题,该示例建立了一个 Tween
对象并计算确切值。
AnimatedLogo
来封装其
Tween
对象,以及其
build()
方法在母动画对象上调用
Tween.evaluate()
来计算所需的尺寸和不透明度值。
下面的代码中将这些改动突出显示。
class AnimatedLogo extends AnimatedWidget { // Make the Tweens static because they don't change. static final _opacityTween = Tween<double>(begin: 0.1, end: 1); static final _sizeTween = Tween<double>(begin: 0, end: 300); AnimatedLogo({Key key, Animation<double> animation}) : super(key: key, listenable: animation); Widget build(BuildContext context) { final Animation<double> animation = listenable; return Center( child: Opacity( opacity: _opacityTween.evaluate(animation), child: Container( margin: EdgeInsets.symmetric(vertical: 10), height: _sizeTween.evaluate(animation), width: _sizeTween.evaluate(animation), child: FlutterLogo(), ), ), ); } } class LogoApp extends StatefulWidget { _LogoAppState createState() => _LogoAppState(); } class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin { Animation<double> animation; AnimationController controller; @override void initState() { super.initState(); controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); animation = CurvedAnimation(parent: controller, curve: Curves.easeIn) ..addStatusListener((status) { if (status == AnimationStatus.completed) { controller.reverse(); } else if (status == AnimationStatus.dismissed) { controller.forward(); } }); controller.forward(); } @override Widget build(BuildContext context) => AnimatedLogo(animation: animation); @override void dispose() { controller.dispose(); super.dispose(); } }
源代码 animate5:
https://github.com/cfug/flutter.cn/tree/master/examples/animation/animate5
3. 下面的步骤
Tweens
建立动画的基础介绍,还有不少其余类可供探索。好比指定
Tween
类,Material Design 特有的动画,
ReverseAnimation
,共享元素过渡(也称为 Hero 动画),物理模拟和
fling()
方法。