你会学到什么
·如何使用动画库中的基本类将动画添加到小部件。
·什么时候使用`AnimatedWidget`vs`AnimatedBuilder`
本教程向您展示如何在Flutter中构建显示动画。在介绍了动画库中的一些基本概念、类和方法之后,它带您完成了5个动画示例。这些示例相互依存,向您介绍动画库的不同方面。
Flutter SDK还提供了内置的显式动画,例如FadeTransition
、SizeTransition
和SlideTransition
。这些简单地动画是通过是设置起点和终点来触发的。它们比此处描述的自定义显式动画更易于实现。
一、基本动画概念和类
重点是什么?
· `Animation`是Flutter动画库中的核心类,它插入用于引导动画的值。
· `Animation`对象知道动画的当前状态(例如,它是开始、停止还是向前或向后移动),但对屏幕上显示的内容一无所知。
· `AnimationController`管理动画。
· `CurvedAnimation`将进程定义为非线性曲线。
· `Tween`在动画对象使用的数据范围之间进行插值。例如,补间可以定义从红色到蓝色或从0到255的插值。
· 使用`Listeners`和`StatusListeners`来监听动画状态的变化。
Flutter中的动画系统是基于类型化的Animation
对象。小部件可以通过读取它们的当前值并监听它们的状态变化,直接将这些动画合并到他们的构建函数中,或者它们可以使用这些动画作为它们传递给其他小部件的更精细动画的基础。
1.1、Animation<double>
在Flutter中,Animation
对象对屏幕上的内容一无所知。Animation
是一个抽象类,它了解其当前值及其状态(已完成或已解除)。一种更常用的动画类型Animation<double>
。
Animation
对象在一定持续时间内按顺序生成两个值之间的插值。Animation
对象的是撒输出可能试试线性、曲线、阶跃函数或您可以设计的任何其他映射。根据Animation
对象的控制方式,它可以反向运行,甚至可以在中间切换方向。
Animations
还可以插入除double
之外的类型,例如,Animation<Color>
或Animation<Size>
。
Animation
对象具有状态。它的当前值始终在.value
成员中可用。
Animation
对象对渲染或build()
函数一无所知。
1.2、CurvedAnimation
CurvedAnimation
将动画的进度定义为非线性曲线。
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
注意: 该类Curves
定义了许多常用曲线,或者您可以创建自己的曲线。例如:
import 'dart:math';
class ShakeCurve extends Curve {
@override
double transform(double t) => sin(t * pi * 2);
}
浏览文档以获取 Flutter 附带的常量Curves
的完整列表(带有可视化预览)
CurvedAnimation
和AnimationController
(在下一节中介绍)都是Animation<double>
类型,因此您可以交替传递它们。CurvedAnimation
包装了它正在修改的对象——您不需要继承AnimationController
来实现曲线。
1.3、AnimationController
AnimationControllre
是一个特殊的Animation
对象,只要硬件准备好接收新帧,它就会生成一个新值。默认情况下,AnimationController
在给定的持续时间内线性生成从0.0到1.0的数字。例如,这段代码创建了一个Animation
对象,但没有启动它运行:
controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);
AnimationController
派生自Animation<double>
,因此可以在需要Animation
对象的任何地方使用它。然而,AnimationController
有额外的方法来控制动画。例如,您使用.forward()
方法启动动画。数字的生成与屏幕刷新有关,因此通常每秒生成60个数值。每个数字生成后,每个Animation
对象都会调用附加的Listener
对象。要为每个子项创建自定义显示列表,请参阅RepaintBoundary
.
创建AnimationController
时,您向它传递一个vsync
参数。vsync
的存在可以防止屏幕外动画消耗不必要的资源。您可以通过将SingleTickerProviderStateMixin
添加到类定义来将状态对象用作vsync
。您可以在Github上的animate1
中看到这方面的示例。
注意:在某些情况下,位置可能会超过`AnimationController`的0.0-1.0范围。例如,`fling()`函数允许您提供双速度、力和位置(通过Force对象)。位置可以是任何东西,因此可以在0.0到1.0范围之外。
`CurvedAnimation`也可以超过0.0到1.0的范围,即使`AnimationController`没有。根据所选曲线,`CurvedAnimation`的输出范围可以比输入范围更宽。例如`Curves.elasticIn`等弹性曲线明显超出或低于默认范围。
1.4、Tween
默认情况下,AnimationController
对象的范围从0.0到1.0。如果您需要不同的范围换或不同的数据类型,您可以使用Tween
配置动画以插入到不同的范围或数据类型 。例如,一下Tween
从-200.0
变为0.0
:
tween = Tween<double>(begin: -200, end: 0);
Tween
是一个无状态对象,只有开始和结束。Tween
的唯一工作是定义从输入范围到输出范围的映射。输入范围通常为0.0
到1.0
,但这不是必须的。
Tween
继承自Animatable<T>
,而不是Animation<T>
。Animatable
和Animation
一样,不必输出双倍。例如,ColorTween
指定两种颜色之间的渐变。
colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);
Tween
对象不存储任何状态。相反,它提供了evaluate(Animation<double> animation)
方法,该方法使用转换函数将动画的当前值(介于0.0和1.0之间)映射到实际动画值。
Animation
对象的当前值可以在.value
方法中找到。evaluate
函数还执行一些内务处理,例如确保在动画值分别为0.0
和1.0
时返回begin和end。
1.4.1、Tween.animate
要使用Tween
对象,请在Tween
上调用animate()
并传入控制器对象。例如,以下代码在500毫秒的过程中生成从0到255的整数值。
AnimationController controller = AnimationController(duration: const Duration(millisecond: 500), vsync: this);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);
注意:`animate()`方法返回一个`Animation`,而不是一个`Animatable`
以下示例显示了一个控制器、一条曲线和一个Tween
:
AnimationController controller = AnimationController(duration: const Duration(millisseconds: 500), vsync: this);
final Animation<double> curve = CurvedAnimation(parent: controller, curve: Curves.easseOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);
1.5、Animation notifications
Animation
对象可以有Listeners
和StatusListeners
,用addListener()
和addStatusListener()
定义。只要动画的值发生变化,就会调用Listener
。Listener
最常见的行为是调用setState()
以引起重建。当动画开始、结束、向前移动或向后移动时调用StatusListener
,如AnimationStatus
所定义。
二、动画示例
本节将向您介绍5个动画示例。每个部分都提供了指向该示例源代码的链接。
2.1、渲染动画
重点是什么?
· 如何使用`addListener()`和`setState()`将基本动画添加到小部件。
· 每次`Animaaation`生成一个新数字时,`addLiistener()`函数都会调用`setStaate()`。
· 如何撒使用所需的`vsync`参数定义`AnimationController`。
· 了解`..addListener`中的`..`语法,也称为Dart的级联符号。
· 要将类设置为私有,其名称应以下划线(_)开头。
到目前为止,您已经了解了如何随时间生成数字序列。没有任何东西被渲染到屏幕身上。要使用Animation
对象进行渲染,请将Animation
对象存储为小部件的成员,然后随时用其值来决定如何绘制。
考虑以下绘制没有动画的Flutter Logo的应用程序:
import 'package:flutter/material.dart';
void main() => runApp(const LogoApp());
class LogoApp extends StatefulWidget {
const LogoApp({Key? key): super(key: key);
@override
State<LogoApp> createState() => _LogoAppState();
}
class _LogoAppSttate extends State<LogoApp> {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: 300,
wiidth: 300,
child: const FlutterLogo(),
);
);
}
}
应用来源: animate0
下面显示了修改后的相同代码,使Logo具有动画效果,从无到有增长的完整大小。在定义AAnimationController
时,必须传入一个vsync
对象。vsync
参数在AnimationController
部分中进行了描述。
突出显示了非动画示例的修改:
addListener()
函数调用setState()
,因此每次Animatioon
生成新数字时,当前帧 都被标记为dirty,这会强制再次调用build()
。在build()
中,容器改变了大小,因为它的高度和宽度现在使用animation.value
而不是硬编码值。在丢弃State
对象时处理控制器,以防止内存泄露。
通过这些是少量更改,您已经在Flutter中创建了您的第一个动画!
Dart语言技巧:您可能不熟悉Dart的级联表示法——..addListener()
中的两个点。此语法意味着使用animate()
的返回值调用addListener()
方法。
以下是示例:
AnimatedWidget
基类允许您将核心小部件代码从动画代码中分离出来。AnimatedWidget
不需要维护一个State
对象来保存动画。添加以下AnimatedLogo
类:
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({super.key, required Animation<double> animation}}: super(listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: const EdgeIInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
}
AnimationedLogo
在绘制自身时使用Animation
的当前值。
LogoApp
仍然管理AnimationController
和Tween
,并将Animation
对象传递给AnimatedLogo
:
2.3、控制动画进度
重点是什么?
· 使用`addStatusListener()`通知动画状态的变化,例如开始、停止或反转方向。
· 当动画完成或返回到其开始状态时,通过反转方向在无线循环中运行动画。
了解动画何时更改状态通常很有帮助,例如完成、前进或倒退。您可以使用addStatusListener()
通知。以下代码修改了前面的示例,以便它侦听状态更改并打印更新。突出显示的行显示更改:
2.4、使用AnimatedBuilder
进行重构
重点是什么?
· `AnimatedBuilder`了解如何呈现过渡。
· `AnimatedBuilder`不知道如何渲染小部件,也不知道如何管理`Animation`对象。
· 使用`AnimatedBuilder`将动画描述为另一个小部件的构建方法的一部分。如果您只是想定义一个带有可重用动画的小部件,请使用`AnimatedWidget`,如使用`AnimatedWidget`进行简化部分所示。
· Flutter API中的`AnimatedBuilder`示例:`BottomSheet`、`ExpansionTile`、`PopupMenu`、`ProgressIndicator`、`ReefreshIndicator`、`Scaffold`、`SnackBar`、`TabBar`、`TextField`。
animate3
示例中的代码存在一个问题,即更改动画需要更改呈现Logo的小部件。更好地解决方案实施将职责分离到不同的类中:
- 渲染Logo
- 定义
Animation
对象 - 渲染过渡
您可以在AnimatedBuilder
类的帮助下完成这种分离。AnimatedBuilder
是渲染树中的一个单独类。与AnimatedWidget
一样,AnimatedWidget
会自动侦听来自Animation
对象的通知,并在必要时将小部件树标记为dirty,因此您无需调用addListener()
。
animate4
示例的小部件树如下所示:
class LogoWidget extends StatelessWidget {
const LogoWidget(Key? key): super(keu: key);
// Leave out the height and width so it fills the animating parent
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10),
child: const FlutterLogo(),
);
}
}
图中间的三块都是在GrowTransition
中的build()
方法中创建的,如下所示。GrowTransition
小部件本身是无状态的,并包含定义过渡动画所需的一组最终变量。build()
函数创建并返回AnimatedBuilder
,它以(Anonymous builder
)方法和LogoWidget
对象作为参数。渲染过渡的工作实际上发生在(Annoymous builder
)方法中,该方法创建一个适当大小的Container
以强制LogoWidget
收缩以适应。
下面代码中的一个棘手点时候子项看起来像是被指定了李爱你告辞。发生的事情是child
的外部引用被传递给AnimatedBuilder
,AnimatedBuilder
将其传递给匿名闭包,然后使用该对象作为其子对象。最终结果是AnimatedBuilder
被插入到渲染树中的两个小部件之间。
class GrowTransition extends StatelessWidget {
const GrowTransition({required this.child, required this.animation});
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return SizedBox(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,
),
);
}
}
最后,初始化动画的代码看起来与animate2
示例非常相似。initState()
方法创建一个AnimationController
和Tween
,然后将它们与animate()
绑定。魔法发生在build()
方法中,该方法返回一个带有LogoWidget
作为子对象的GrowTransition
对象,以及一个驱动转换的动画对象。这些是上面要点中列出的三个要素。
2.5、同步动画
重点是什么?
· `Curves`类定义可与`CurvedAnimation`一起随时用的常用曲线数组
在本节中,您将以监控动画进度,该示例使用AnimatedWidget
来连续动画进出。考虑这样一种情况,当不透明度从透明度变为不透明时,您想要动画进出。
注意:此示例展示了如何在同一个动画控制器上使用多个补间,其中每个补间管理动画中的不同效果。它仅用于说明目的。如果您在生产代码中补间不透明度和大小,您可能会改用`FadeTransition`和`SizeTransition`。
每个补间管理动画的一个方面。例如:
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()
来计算所需的大小和不透明度值。以下代码突出显示了更改: