我们知道你在飞行时有很多选择,我的意思是动画,在 Flutter 中,所以感谢你选择 AnimatedBuilder
和 AnimatedWidget
。 等等,什么? 不! Flutter 有许多不同的动画小部件,但与商业航空公司不同的是,每种类型的小部件都适合不同类型的工作。 当然,您可以通过几种不同的方式完成相同的动画,但是使用正确的动画小部件来完成这项工作将使您的生活更轻松。
We know you have many choices when you fly, I mean animate, in Flutter, so thank you for choosing
AnimatedBuilder
andAnimatedWidget
. Wait, what? No! Flutter has many different animation widgets, but unlike commercial airlines, each type of widget is right for a different type of job. Sure, you can accomplish the same animation in a couple of different ways, but using the right animation widget for the job will make your life much easier.
本文介绍了为什么您可能想要使用 AnimatedBuilder
或 AnimatedWidget
与其他动画小部件,以及如何使用它们。 假设您想向您的应用程序添加动画。 本文是系列文章的一部分,介绍您可能想要使用每种类型的动画小部件的原因。 您想要执行的特定动画会重复几次,或者需要能够暂停和恢复以响应某些事情,例如手指点击。 因为您的动画需要重复或停止和开始,所以您需要使用显式动画。
This article covers why you might want to use
AnimatedBuilder
orAnimatedWidget
versus other animation widgets, and how to use them. Suppose you want to add an animation to your app. This article is part of a series, walking through why you might want to use each type of animation widget. The particular animation you want to do repeats a couple of times or needs to be able to pause and resume in response to something, like a finger tap. Because your animation needs to repeat, or stop and start, you’ll need to use an explicit animation.
提醒一下,我们在 Flutter 中有两大类动画:显式和隐式。 对于显式动画,您需要一个动画控制器。 对于隐式动画,您不需要。 我们在上一篇关于内置显式动画的文章中介绍了动画控制器。 如果您想了解有关这些的更多信息,请先查看。
As a reminder, we have two broad categories of animations in Flutter: explicit and implicit. For explicit animations, you need an animation controller. For implicit animations, you don’t. We introduced animation controllers in the previous article about built-in explicit animations. If you’d like to learn more about those, please check that out first.
所以,如果你确定你需要一个显式动画,有很多显式动画类供你选择。 这些是通常名为 FooTransition
的类,其中 Foo 是您要设置动画的属性的名称。 我建议先看看您是否可以使用这些小部件之一来满足您的需求,然后再深入研究 AnimatedWidget
和 AnimatedBuilder
。 几乎所有你能想到的小部件都有一个惊人的选择——旋转、位置、对齐、淡入淡出、文本样式等等。 另外,您可以组合这些小部件,以便您可以旋转和淡入淡出。 但是,如果这些内置小部件都不能满足您的需求,那么是时候使用 AnimatedWidget
或 AnimatedBuilder
构建您自己的小部件了。
So, if you’ve determined that you need an explicit animation, there are a whole host of explicit animation classes for you to choose from. Those are the classes generally named
FooTransition
, where Foo is the name of the property you are trying to animate. I recommend seeing if you can use one of those widgets to accomplish your needs first, before diving into the deep world ofAnimatedWidget
andAnimatedBuilder
. There’s an amazing selection of widgets for pretty much anything you can think of — rotation, position, alignment, fading, text style, and many more. Plus, you can compose these widgets, so that you can rotate and fade. But, if none of those built-in widgets can do what you’re looking for, it’s time to build your own usingAnimatedWidget
orAnimatedBuilder
.
用于了解在任何场景中使用什么动画的流程图。 本文重点介绍两个底部蓝色的结束状态,AnimatedBuilder 和 AnimatedWidget。
A flow chart for understanding what animation to use in any scenario. This article focuses on the two bottom blue end-states, AnimatedBuilder and AnimatedWidget.
具体例子
Specific Example
为了更具体,让我们通过一个特定的场景:我想用外星飞船编写一个应用程序,并有一个飞船光束动画。
To make this more concrete, let’s walk through a specific scenario: I want to write an app with an alien spaceship and have a spaceship beam animation.
大概就是这样。
Maybe something exactly like this.
我画了一个带有渐变的宇宙飞船光束,从黄色渐变到透明,渐变的中心开始渐变。 然后,我使用路径剪刀从该渐变创建了“光束”形状。
I drew a spaceship beam with a gradient that fades from yellow to transparent, beginning the fade at the very center of the gradient. Then, I created the “beam” shape from that gradient with a path clipper.
我想创建一个“光束向下”动画,从渐变的中心开始,我想让它重复。 这意味着我需要创建一个显示动画。 不幸的是,没有内置的显式动画来为漏斗形渐变设置动画,但你知道我们有什么… AnimatedWidget
和 AnimatedBuilder
来实现这一点!
I want to create a “beam down” animation, starting from the center of that gradient, and I want to make it repeat. That means I’ll need to create an explicit animation. Unfortunately there’s no built-in explicit animation to animate funnel-shaped gradients, but you know what we do have…
AnimatedWidget
andAnimatedBuilder
to do the trick!
AnimatedBuilder
AnimatedBuilder
为了使光束具有动画效果,我将把这个渐变代码包装在一个 AnimatedBuilder
小部件中。 渐变代码位于 builder 函数中,该函数在 AnimatedBuilder
构建时被调用。
To make the beam animate, I am going to wrap this gradient code in an AnimatedBuilder widget. The gradient code goes in the builder function, which gets called when the AnimatedBuilder builds.
接下来我需要添加一个控制器来驱动这个动画。 控制器提供 AnimatedBuilder
用于逐帧绘制新版本动画的值。 正如您在上一篇文章中看到的,我混合了 SingleTickerProviderStateMixin
类并在 initState
函数中实例化控制器,以便它只创建一次。 我在 initState
中创建控制器,而不是在 build
方法中,因为我不想多次创建这个控制器——我希望它为每一帧提供新的动画值! 因为我在 initState
中创建了一个新对象,所以我添加了一个 dispose 方法并告诉 Flutter 当父小部件不再出现在屏幕上时它可以摆脱该控制器。
Next I need to add a controller to drive this animation. The controller provides the values that
AnimatedBuilder
uses to draw new versions of the animation frame by frame. As you saw in the previous article, I mix in theSingleTickerProviderStateMixin
class and instantiate the controller in theinitState
function so that it only gets created once. I create the controller ininitState
, rather than thebuild
method, because I don’t want to create this controller multiple times — I want it to provide new values to animate with for each frame! Because I created a new object ininitState
, I add a dispose method and tell Flutter that it can get rid of that controller when the parent widget is no longer on the screen.
然后,我将该控制器传递给 AnimatedBuilder,我的动画按预期运行!
Then, I pass that controller to the AnimatedBuilder, and my animation runs as expected!
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
final Image starsBackground = Image.asset(
'assets/milky-way.jpg',
);
final Image ufo = Image.asset('assets/ufo.png');
AnimationController _animation;
@override
void initState() {
super.initState();
_animation = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..repeat();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
starsBackground,
AnimatedBuilder(
animation: _animation,
builder: (_, __) {
return ClipPath(
clipper: const BeamClipper(),
child: Container(
height: 1000,
decoration: BoxDecoration(
gradient: RadialGradient(
radius: 1.5,
colors: [
Colors.yellow,
Colors.transparent,
],
stops: [0, _animation.value],
),
),
),
);
},
),
ufo,
],
);
}
@override
void dispose() {
_animation.dispose();
super.dispose();
}
}
class BeamClipper extends CustomClipper<Path> {
const BeamClipper();
@override
getClip(Size size) {
return Path()
..lineTo(size.width / 2, size.height / 2)
..lineTo(size.width, size.height)
..lineTo(0, size.height)
..lineTo(size.width / 2, size.height / 2)
..close();
}
/// Return false always because we always clip the same area.
@override
bool shouldReclip(CustomClipper oldClipper) => false;
}
您可能还记得在 TweenAnimationBuilder 文章中我们使用 child 参数作为性能优化,我们也可以使用 AnimatedBuilder
来做到这一点。 基本上,如果我们有一个在动画过程中永远不会改变的对象,我们可以提前构建它,然后将它传递给 AnimatedBuilder
。
You may recall in the TweenAnimationBuilder article that we used the child parameter as a performance optimization, and we can do that with AnimatedBuilder too. Basically, if we have an object that never changes over the course of the animation, we can build it ahead of time, and just pass it to AnimatedBuilder.
在这种特定情况下,完成相同事情的更好方法是为 BeamClipper 提供一个 const 构造函数并使其成为 const。 它的代码更少,并且对象是在编译时创建的,从而使事情变得更快。 但是,有时您会编写一些没有 const 构造函数的代码,这是何时使用该可选child参数的好例子。
In this specific case, a better way to accomplish the same thing is to give BeamClipper a const constructor and just make it const. It’s less code, and the object is created at compile time, making things even faster. Sometimes, though, you’ll be coding something that doesn’t have a const constructor, and that’s a good case for when to make use of that optional child parameter.
AnimatedWidget
所以,我们有了动画,但是包含 AnimatedBuilder
代码的 build 方法有点大。 如果您的构建方法开始变得难以阅读,那么是时候重构您的代码了!
So, we have our animation, but the build method that contains the
AnimatedBuilder
code is a little large. If your build method is starting to get hard to read, it’s time to refactor your code!
您可以将 AnimatedBuilder
代码拉出到一个单独的小部件中,但是然后在构建方法中只有一个构建方法,这有点愚蠢。 相反,您可以通过创建一个扩展 AnimatedWidget
的新小部件来完成相同的动画。 我将我的小部件 BeamTransition
命名为与显式动画的 FooTransition 命名约定一致。 我将动画控制器传递给 BeamTransition
并重用 AnimatedBuilder
的构建器函数的主体。
You could pull your
AnimatedBuilder
code out into a separate widget, but then you just have a build method inside a build method, which is a little silly. Instead, you can accomplish the same animation by creating a new widget that extendsAnimatedWidget
. I’ll name my widgetBeamTransition
to be consistent with the FooTransition naming convention for explicit animations. I pass in the animation controller to BeamTransition and reuse the body of theAnimatedBuilder’s
builder function.
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
final Image starsBackground = Image.asset(
'assets/milky-way.jpg',
);
final Image ufo = Image.asset('assets/ufo.png');
AnimationController _animation;
@override
void initState() {
super.initState();
_animation = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..repeat();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
starsBackground,
BeamTransition(animation: _animation),
ufo,
],
);
}
@override
void dispose() {
_animation.dispose();
super.dispose();
}
}
class BeamTransition extends AnimatedWidget {
BeamTransition({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return ClipPath(
clipper: const BeamClipper(),
child: Container(
height: 1000,
decoration: BoxDecoration(
gradient: RadialGradient(
radius: 1.5,
colors: [
Colors.yellow,
Colors.transparent,
],
stops: [0, animation.value],
),
),
),
);
}
}
就像AnimatedBuilder
一样,如果合适的话,我可以在我的小部件中添加一个child参数作为性能优化,这样它就可以提前构建,而不是每次执行动画都构建一次。不过提醒一下,在本例中,让我的BeamClipper
采用const
构造函数是最好的方法。
Just like AnimatedBuilder, if appropriate, I can add a child parameter to my widget as a performance optimization so that it builds ahead of time, instead of every time I animate. Just a reminder though, in this example, making my BeamClipper take a const constructor is the best way to go.
所以真的,我应该使用哪一个?
So really, which one should I to use?
我们刚刚看到,当您无法找到一个内置的显式动画来做您想要的事情时,如何使用AnimatedBuilder
和AnimatedWidget
来完成相同类型的显式动画。那么,你应该使用哪一种呢?这真的是一个品味的问题。一般来说,我建议制作单独的小部件,每个小部件都有一个单独的任务——在本例中是动画。
We just saw how
AnimatedBuilder
andAnimatedWidget
can both be used to accomplish the same type of explicit animations when you can’t find a built-in explicit animation to do what you want. So, which one should you use? It’s really a matter of taste. Generally speaking, I recommend making individual widgets, each with a separate job — which in this case is animation.
这是对尽可能使用 AnimatedWidget
的投票。 但是,如果您创建动画控制器的父小部件非常简单,那么为您的动画制作一个单独的独立小部件可能需要太多额外的代码。 在这种情况下,AnimatedBuilder
就是您所需要的。
That’s a vote in favor of using
AnimatedWidget
whenever possible. However, if your parent widget that creates the animation controller is pretty simple, maybe making a separate standalone widget just for your animation is too much extra code. In that case,AnimatedBuilder
is all that you need.
这是下面视频的文章版本。 如果你喜欢视频,请点击下面:
This is an article version of the video below. If you prefer videos, click below:
Creating custom explicit animations with AnimatedBuilder & AnimatedWidget - Flutter in Focus
本系列文章:
Articles in this series:
- 如何选择适合你的 Flutter Animation Widget?
- Flutter 动画基础与隐式动画
- Flutter 中的自定义隐式动画……使用 TweenAnimationBuilder
- 带有内置显式动画的定向动画
- 我什么时候应该使用 AnimatedBuilder 或 AnimatedWidget?(这遍文章)
- 深入动画
- How to Choose Which Flutter Animation Widget is Right for You?
- Flutter animation basics with implicit animations
- Custom Implicit Animations in Flutter…with TweenAnimationBuilder
- Directional animations with built-in explicit animations
- When should I use AnimatedBuilder or AnimatedWidget?(this article)
- Animation deep dive