如果我们想要一些东西动画,我们必须改变大小或改变连续帧中对象的位置。例如,在第1帧中,我们的对象位于位置x,在第2帧中,它将位于x + 1的位置,在第3帧中,它位于x + 2的位置,依此类推。
创建动画时的另一个概念是“每秒帧数”或FPS。我们想要每秒更改对象的位置或大小多少次?电影通常每秒使用24帧。这是人类眼睛看起来光滑自然的动画的最小数量。
FPS(图片来源)
为了在Flutter中为小部件设置动画,我们需要以下小部件:
-
Animation<T>:动画对象由值(类型
T
)和状态组成。该值类似于当前帧编号。它会告诉您是否在第1,2,3等帧中。根据此值,您可以决定窗口小部件的下一个位置或大小。状态指示动画在概念上是从开始到结束还是从结束回到开始。 - AnimationController:要创建动画,首先要创建一个AnimationController。在给定的持续时间内,此小部件线性生成从0.0(下限)到1.0(上限)的值。只要运行应用程序的设备准备好显示新帧(通常,此速率大约为每秒60个值),动画控制器就会生成一个新值。一个AnimationController当不再需要它应该被设置。这减少了泄漏的可能性。当与StatefulWidget一起使用时,通常在State.initState方法中创建AnimationController ,然后将其放置在State.dispose中。方法。请注意,AnimationController继承了Animation类,因此属于Animation类型。
- Tween:这个类可用于将1AnimationController1的下界和上界(默认值为0.0到1.0)从开始到结束转换(或映射)到值。除非另有说明,否则吐Tween为double类型。补间的唯一工作是定义一个从输入范围到输出范围的映射。输入范围通常是0.0到1.0,但这不是必需的。
-
TickerProvider:这是一个生成Ticker对象的工厂。Ticker对象为每个新帧触发一个事件。AnimationController类使用Ticker来逐步调整它控制的动画。我们可以通过使用
SingleTickerProviderStateMixin
实现TickerProvider功能,将Ticker功能添加到我们的有状态类中。如果您不确定mixins是什么,请阅读本文。当您只需要一个Ticker对象时(例如,如果类在其整个生命周期内仅创建一个AnimationController),此mixin非常有用。 - AnimatedBuilder:很明显,每当我们改变小部件的大小或位置时,我们都想重新构建它。但是我们怎么做到的?这是AnimatedBuilder小部件派上用场的地方。我们给这个小部件提供动画,并告诉它在动画前进时要绘制什么。
好吧,让我们做一些实际的事。
首先,我们将在屏幕中间创建一个圆圈,并使其周期性地变大。
我们的主要功能运行应用程序并显示AnimatedCirclePage
:
main.dart
import 'package:animation/pages/animated_circle_page.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: AnimatedCirclePage(),
);
}
}
在AnimatedCirclePage
最初显示在页面中间的圆圈:
animated_circle_page.dart
import 'package:flutter/material.dart';
class AnimatedCirclePage extends StatefulWidget {
@override
_AnimatedCirclePageState createState() => _AnimatedCirclePageState();
}
class _AnimatedCirclePageState extends State<AnimatedCirclePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Animated Circle"),
),
body: Center(
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(25),
),
color: Colors.red,
),
),
),
);
}
}
现在让我们用动画放大这个圆圈。
在下面的代码中,我们添加了一个类型的成员AnimationController
并在initState
方法中实例化它。AnimationController
需要两个参数:Duration
和TickerProvider
。
duration参数指定动画将持续多长时间,在我们的示例中,将需要1秒钟才能完成。
第二个参数被命名vsync
为type TickerProvider
。由于我们TickerProvider
使用以下mixin为我们的类添加了功能:
with SingleTickerProviderStateMixin
该类的当前实例可以vsync
作为TickerProvider
:传递给参数:
vsync: this
因此我们将:
animated_circle_page.dart
class _AnimatedCirclePageState extends State<AnimatedCirclePage>
with SingleTickerProviderStateMixin {
AnimationController animationController;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
animationController.forward();
}
调用该forward
方法将启动动画并生成从0.0(下限)到1.0(上限)的值。但是我们如何消耗生成的价值呢?
使用AnimatedBuilder小部件!
animated_circle_page.dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Animated Circle"),
),
body: AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
final size = 100 * (animationController.value+1);
return Center(
child: Container(
width: size,
height: size,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(size/2),
),
color: Colors.red,
),
),
);
},
),
);
}
AnimationBuilder的构造函数有三个参数:
-
animation
:我们在这里提供动画对象。请记住,AnimationController继承自Animation类。因此,我们的AnimationController属于Animation类型,可以传递给此参数。 -
child
:此可选参数是一个小部件,在动画期间不会更改,只创建一次(以提高性能)。它总是可以在构建器函数(下一个参数)中重用。 -
builder
:这是为动画的每个刻度调用的函数。在这里,我们可以决定在动画的下一帧中绘制什么。我们可以通过animation.value
属性访问当前帧号。
一旦我们调用该animationController.forward()
方法,我们的动画就会启动,并且将为动画的每个帧调用AnimatedBuilder的构建器方法。在每个帧中,值animationController.value
将逐渐从0.0增加到1.0。我们可以利用这个值并根据它改变圆的宽度和高度:
final size = 100 * (animationController.value+1);
如您所见,当动画值为0.0时,圆的大小将为100,当值增加到1.0时,大小将更改为200.因此,我们必须看到以下动画,其中圆的大小从100变为100到200:
扩大圈
在上面的示例中,我们为所有动画值添加了1。实际上,我们需要将动画的范围从[0.0 ... 1.0]更改为[1.0 ... 2.0]。你还记得这Tween
堂课对什么有用吗?它用于修改动画值。所以我们可以在这里使用Tween类将动画值从[0.0 ... 1.0]映射到[1.0 ... 2.0]。我们开始做吧:
animated_circle_page.dart
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(
seconds: 1,
),
vsync: this,
);
animation = Tween(begin: 1.0, end: 2.0).animate(animationController);
animationController.forward();
}
映射动画值实际上发生在以下行中:
animation = Tween(begin:1.0, end:2.0).animate(animationController);
我们创建一个实例Tween
并指定begin
和end
值。然后我们调用该animate
方法并将动画对象传递给它。animate函数会返回一个新的animation
对象,其值是从begin
到end
。然后在AnimatedBuilder对象中,我们只需将animation属性设置为我们的新animation
对象:
body: AnimatedBuilder(
animation: animation,
并将圆的大小设置为:
final size = 100 * (animation.value);
反转动画
现在让我们做一些有趣的事情。一旦我们的动画完成,我们将反转动画(这次它的值从1变为0)。这将使圆圈再次变小。
我们怎么知道我们的动画是完整的?
通过监听AnimationStatus
!
我们可以为动画添加一个监听器,这样每次状态改变时,我们都会收到通知。
animated_circle_page.dart
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(
seconds: 1,
),
vsync: this,
);
animation = Tween(begin: 1.0, end: 2.0).animate(animationController);
animationController.addStatusListener(animationStatusListener);
animationController.forward();
}
void animationStatusListener(AnimationStatus status) {
if (status == AnimationStatus.completed) {
animationController.reverse();
} else if (status == AnimationStatus.dismissed) {
animationController.forward();
}
}
只有在状态发生变化时才会调用我们的侦听器函数。动画有四种可能的状态:
- dismissed:动画在开始时停止
- forward:动画从头到尾运行
- reverse:动画从头到尾向后运行
- completed:动画在结束时停止
在上面的代码中,我们检查了状态。如果是completed
,那意味着我们刚刚到达动画的末尾,所以我们调用reverse
函数,向后播放动画。当状态变为dismissed
,表示动画已经到达开头,所以我们forward
再次打电话!这个循环将永远持续下去!
生成的动画是一个连续大小的圆圈:
现在让我们做一些更有趣的事情。我们将围绕屏幕中心旋转这个圆圈!
rotating_circle_page.dart
使用以下代码创建一个名为的新页面:
import 'package:flutter/material.dart';
class RotatingCirclePage extends StatefulWidget {
@override
_RotatingCirclePageState createState() => _RotatingCirclePageState();
}
class _RotatingCirclePageState extends State<RotatingCirclePage>
with SingleTickerProviderStateMixin {
Widget _buildCircle(radius) {
return Container(
width: radius * 2,
height: radius * 2,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(radius),
),
color: Colors.red,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Rotating Circle"),
),
body: Align(
alignment: Alignment(0, -0.1),
child: _buildCircle(30.0),
),
);
}
}
为了使代码更具可读性,我创建了一个辅助函数_buildCircle
,用于绘制具有给定半径的红色圆圈。而不是将圆圈居中,我将它对齐在页面中心的上方。(如果您不熟悉,请观看谷歌的这个简短视频以熟悉Align
小部件)。结果是:
现在让我们像钟摆一样为这个圆圈制作动画:
animated_circle_page.dart
import 'package:flutter/material.dart';
import 'dart:math' as math;
class RotatingCirclePage extends StatefulWidget {
@override
_RotatingCirclePageState createState() => _RotatingCirclePageState();
}
class _RotatingCirclePageState extends State<RotatingCirclePage>
with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation animation;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(
seconds: 2,
),
vsync: this,
);
animation = CurvedAnimation(
parent: animationController,
curve: Curves.fastOutSlowIn,
);
animationController.addStatusListener(animationStatusListener);
animationController.forward();
}
void animationStatusListener(AnimationStatus status) {
if (status == AnimationStatus.completed) {
animationController.reverse();
} else if (status == AnimationStatus.dismissed) {
animationController.forward();
}
}
Widget _buildCircle(radius) {
return Container(
width: radius * 2,
height: radius * 2,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(radius),
),
color: Colors.red,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Rotating Circle"),
),
body: Align(
alignment: Alignment(0, -0.1),
child: AnimatedBuilder(
child: _buildCircle(30.0),
animation: animationController,
builder: (BuildContext context, Widget child) {
return Transform.rotate(
child: child,
angle: math.pi * 2 * animation.value,
origin: Offset(0, 30),
);
},
),
),
);
}
}
让我解释一下上面代码的重要部分。
animation = CurvedAnimation(
parent: animationController,
curve: Curves.fastOutSlowIn,
);
默认情况下,AnimationController
在给定的持续时间内线性生成从0.0到1.0的数字,因此动画无任何速度播放。如果我们想要改变动画的速度和样式,我们可以将它包装在CurvedAnimation小部件中:
当您想要将非线性曲线应用于动画对象时,CurvedAnimation非常有用,特别是当您想要动画前进时的曲线与后退时的曲线时。
请注意,可以首先在CurvedAnimation小部件中包装动画,然后使用Tween小部件转换其下限和上限,如下所示:
animation = Tween(begin: 5.0, end: 10.0).animate(
CurvedAnimation(
parent: animationController,
curve: Curves.fastOutSlowIn,
),
);
在这里,我使用了曲线。fastOutSlowIn曲线,但您可以使用其他值并查看它们如何影响动画的速度和速度。
现在让我解释一下构建方法:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Rotating Circle"),
),
body: Align(
alignment: Alignment(0, -0.1),
child: AnimatedBuilder(
child: _buildCircle(30.0),
animation: animationController,
builder: (BuildContext context, Widget child) {
return Transform.rotate(
child: child,
angle: math.pi * 2 * animation.value,
origin: Offset(0, 30),
);
},
),
),
);
}
我们已将该child
属性设置AnimatedBuilder
为圆圈。为什么?因为我们希望它只创建一次,而不是每一帧!(为了提高性能,我们不需要重建不随时间变化的动画部分。这里我们的圆圈大小在动画期间保持不变,所以我们只构建它一次并将它分配给孩子AnimatedBuilder
。builder
每次绘制新帧时,该子项都可以在方法中重复使用。
在builder
我们动画的每一帧都会调用的方法中,我们习惯Transform.rotate
了旋转圆圈。如果我们不指定origin
参数,圆圈将围绕其自身的中心旋转(在这种情况下,由于圆圈围绕其自身的中心旋转,我们将看不到任何旋转!)。出于这个原因,我们将旋转中心设置为偏移(0,30)表示的点,该点是距离窗口小部件中心的x距离为0,y距离为30的点。请查看以下内容图片。圆圈现在将围绕标有X的原点旋转:
旋转角度已设置为:
angle: math.pi * 2 * animation.value,
由于动画值从0变为1,旋转角度将从0变为2π,这等于完整的360°旋转。
重要说明:我没有包含_buildCircle(30.0)
在Align小部件中。相反,我已经在Align小部件中包装了整个动画,这是我们的AnimatedWidget对象。那是因为我们只想旋转圆圈,而不是旋转它周围的空间!如果我们在Align小部件中包装了AnimatedBuilder的子节点,那么我们的圆圈周围会有一个额外的空间,这会导致我们的计算出错。我的全部观点是以下代码是错误的:
animated_circle_page.dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Rotating Circle"),
),
body: AnimatedBuilder(
child: Align(
alignment: Alignment(0, -0.1),
child: _buildCircle(30.0),
),
animation: animationController,
builder: (BuildContext context, Widget child) {
return Transform.rotate(
child: child,
angle: math.pi * 2 * animation.value,
origin: Offset(0, 30),
);
},
),
);
}
与上一个动画一样,当动画完成时,我们将其反转,使其从2π旋转回0 度。结果是以下动画:
圆周围原点旋转
在上一篇文章中,我们学习了如何绘制弯曲的虚线。我告诉过你关于创建以下动画的信息:
我将在github上的本文代码中包含上述动画的源代码。但我建议你自己创作作为家庭作业!请注意,我没有在弯曲的路径上移动碟子。我正在使用Transform.translate小部件在两条独立的直线上设置动画。Transform.translate
可用于在绘制对象之前dx
和dy
之前偏移对象。
摘要,回顾和最终说明
而已。你可以在这里停止阅读!我只想强调以下注释,我从Flutter提供的关于动画的三篇文章中抓取了这些文章(阅读这些文章很好):
要创建动画,首先要创建一个AnimationController。除了作为动画本身,还AnimationController
可以控制动画。例如,您可以告诉控制器向前播放动画或停止动画。
AnimationController是一个特殊Animation
对象,只要硬件准备好新帧,它就会生成一个新值。默认情况下,AnimationController
在给定的持续时间内线性生成从0.0到1.0的数字。
AnimationController
派生自Animation<double>
,因此它可以在需要Animation
对象的任何地方使用。但是,AnimationController
还有其他控制动画的方法。例如,您使用该.forward()
方法启动动画。数字的生成与屏幕刷新有关,因此通常每秒生成60个数字。
的[Tween(https://api.flutter.dev/flutter/animation/Tween-class.html)抽象类中的类型值的范围0.0-1.0映射名义上的双精度值(例如Color
,或另一种双)。这是一个Animatable
。要设置超过0.0到1.0间隔的动画,可以使用a [Tween<T>](https://api.flutter.dev/flutter/animation/Tween-class.html)
,它在其开始值和结束值之间进行插值。许多类型都有特定的Tween
子类,它们提供特定于类型的插值。例如,ColorTween在颜色之间插值,RectTween在矩形之间插值。A Tween
继承自Animatable<T>
而非继承Animation<T>
。像动画一样的Animatable不必输出double。例如,ColorTween
指定两种颜色之间的进展:
colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);
可以在Github上找到本文的源代码。
动画菜的源代码可以在这里找到。
谢谢阅读!
转:https://medium.com/@meysam.mahfouzi/understanding-animations-in-flutter-b8ec789d94a4
网友评论