带有内置显式动画的定向动画

要以视频形式观看这篇文章,请在此处查看我们的 YouTube 视频。

To watch this post in video form, check out our YouTube video here.

你好! 在我们之前的文章中,我们学习了如何使用 Flutter 的隐式动画制作一些很棒的动画。 AnimatedFooTweenAnimationBuilder 使您能够将一些基本动画放入您的应用程序中。 这些动画通常朝着一个方向进行,从开始到结束“补间”,然后停止。 在幕后,Flutter 正在接管控制,假设你的意图并处理您担心从一件事到另一件事的过渡的任何需要。

Hi! In our previous posts, we learned how to do some awesome animations using Flutter’s implicit animations. AnimatedFoo and TweenAnimationBuilder gave you the ability to drop some basic animations into your app. These animations typically go in one direction, “tweening” from a start to an end, where they stop. Behind the scenes, Flutter is taking control, assuming intentions and disposing of any need for you to worry about the transition from one thing to the next.

这对于许多动画目标都非常有效,但有时时间的不断向前会让我们感到时间被锁定。 所以,当我们停下来思考热力学定律和宇宙不可避免的热死时,如果我们能倒转时间,然后再做一遍,不是很好吗?

This works perfectly for many animation goals, but sometimes that ever-forward arrow of time leaves us feeling temporally locked. So, as we pause and contemplate the laws of thermodynamics and the inevitable heat death of the universe, wouldn’t it be nice if we could reverse time, and do it all again?

进入我们对 Flutter 显式动画的首次尝试! 我们今天不会构建任何时间机器,但我们将学习如何使用 Transition 小部件更好地控制您的动画。

Enter our first foray into Flutter’s explicit animations! We won’t be building any time machines today, but we will be learning how to gain a bit more control over your animations using Transition widgets.

Transition 小部件是一组 Flutter 小部件,它们的名称都以 - 你猜对了 - Transition 结尾。 ScaleTransitionDecoratedBoxTransitionSizeTransition 等。 它们的外观和感觉很像我们的 AnimatedBlah 小部件。 例如,PositionedTransition 为小部件在不同位置之间的过渡设置动画。 这很像 AnimatedPositioned,但有一个主要区别:这些 Transition 小部件是 AnimatedWidget 的扩展。 这使它们成为显式动画。

Transition widgets are a set of Flutter widgets whose names all end in — you guessed it —Transition. ScaleTransition, DecoratedBoxTransition, SizeTransition, and more. They look and feel a lot like our AnimatedBlah widgets. PositionedTransition, for example, animates a widget’s transition between different positions. This is much like AnimatedPositioned, but there is one major difference: these Transition widgets are extensions of AnimatedWidget. This makes them explicit animations.

但是,对于应用开发者来说,这意味着什么呢?让我们来看看这些动画是如何运行的。

But, what does that really mean for us as app developers? Let’s step through what makes these animations tick.

在这里,我们将使用这个起始图像创建一个星系比例的动画。但是,在这个初始的无动画状态下,感觉不太像银河。我们的第一个任务:混合一些旋转。

Here, we’ll be creating an animation of galactic proportions, using this starting image. But, in this initial unanimated state, it doesn’t feel very galactic. Our first quest: to mix in some rotation.

在这里插入图片描述
一张菲茨星系的照片就在那里,没有旋转。

An image of Fitz’s galaxy just sitting there, not rotating.

RotationTransition 为例

RotationTransition as an example

RotationTransition小部件是一个方便的小部件,它处理所有的三角函数和数学变换,使事物旋转。它的构造函数只需要三件事:

The RotationTransition widget is a handy one that takes care of all of the trigonometry and transformations math to make things spin. Its constructor only takes three things:

// [Most of] RotationTransition’s constructor
RotationTransition({
  Widget child,
  Alignment alignment,
  Animation<double> turns,
})

第一个是child——我们想要旋转的小部件。星系是合适的,所以我们把它放在这里:

First is a child —the widget we want to rotate. The galaxy fits, so we’ll put it there:

RotationTransition(
  child: GalaxyFitz(),
  alignment: null, /*TODO*/
  turns: null, /*TODO*/
)

接下来,我们需要给 RotationTransition 指定我们的星系旋转的点。 我们银河系的黑洞大致位于我们通常预期的图像中间。 所以,我们将给出一个居中的 alignment,使我们所有的旋转数学“对齐”到那个点。

Next, we need to give RotationTransition the point our galaxy rotates around. Our galaxy’s black hole is roughly in the middle of the image where we’d normally expect. So, we’ll give an alignment of center, making all of our rotational math “aligned” to that point.

RotationTransition(
  child: GalaxyFitz(),
  alignment: Alignment.center,
  turns: null, /*TODO*/
)

最后,这个神秘命名的turns属性是什么? API 文档告诉我们这是……动画?!? 我们不是在制作动画吗?

Last, what is this mysteriously named turns property? The API docs tell us this is… an Animation?!? Weren’t we creating an animation?

在这里插入图片描述
RotationTransition 文档告诉我们turns 是Animation 类型。

The RotationTransition docs tell us that turns is of type Animation.

不要担心! 这是使 RotationTransition 和所有其他 Transition 小部件成为显式动画的部分原因。 我们可以使用 AnimatedContainer 和变换来完成相同的旋转效果,但是我们会旋转一次然后停止。 通过我们的显式动画,我们可以控制时间并可以使我们的星系永不停止旋转。

Not to worry! This is part of what makes RotationTransition, and all the other Transition widgets, an explicit animation. We could accomplish the same rotation effect with an AnimatedContainer and a transform, but then we’d rotate once and then stop. With our explicit animations, we have control of time and can make it so that our galaxy never stops spinning.

在这里插入图片描述

今日天文小贴士:大多数星系完成一圈旋转所需的时间略长于 5 秒。

Astronomical tip of the day: Most galaxies take a bit longer than 5 seconds to complete one rotation.

Turns 属性期望给它一个值并在该值发生变化时通知它。 Animation<double> 就是这样。 对于 RotationTransition,该值对应于我们旋转了多少次,或者更具体地说,完成了一个旋转的百分比。

The turns property expects something that gives it a value and notifies it when that value changes. An Animation<double> is just that. For RotationTransition, the value corresponds to how many times we’ve turned, or more specifically, the percentage of one rotation completed.

在这里插入图片描述

太阳系需要大约 3000 万年才能完成 12.6% 的银河系自转。 我们的 Flutter 星系将比这稍快旋转。

It would take the solar system around 30 million years to complete 12.6% of a rotation around the Milky Way. Our Flutter Galaxy will spin slightly faster than that.

创建AnimationController

Creating an AnimationController

获取 Animation<double> 的最简单方法之一是创建 AnimationController,它是动画的控制器。 这个控制器处理 ticks¹ 的监听,并为我们提供了对动画正在做什么的一些有用的控制。

One of the easiest ways to get an Animation<double> is to create an AnimationController, which is a controller for an animation. This controller handles listening for ticks¹ and gives us some useful controls over what the animation is doing.

我们需要在有状态的小部件中创建它,因为在不久的将来保持控制器的句柄将很重要。 因为AnimationController也有自己的状态需要管理,我们在initState中初始化,在dispose中dispose。

We’ll need to create this in a stateful widget because keeping a handle on the controller will be important in our not-too-distant future. Because AnimationController also has its own state to manage, we initialize it in initState, and dispose of it in dispose.

class _TimeMachineState extends State<TimeMachine> {
  AnimationController _animationController;
 
  @override
  void initState() {
    super.initState();
    
    _animationController = AnimationController(
      // ...
    );
  }
  
  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
}

我们必须给 AnimationController 的构造函数提供两个参数。 第一个是duration,也就是我们动画持续的时间。 我们在这里的全部原因是我们需要一个对象来告诉我们在一次旋转中我们走了多远。 默认情况下,AnimationController “发出”从 0.01.0 的值。 这些值的数量和粒度取决于我们希望单次旋转需要多长时间。 幸运的是,Dart 为我们提供了一个 Duration 类来使用。 为了这个演示,我们应该让星系每旋转 5 秒到 2.3 亿年。 那么每转(turn)15秒怎么样?

There are two parameters we must give to AnimationController’s constructor. The first is a duration, which is how long our ̶t̶i̶m̶e̶ ̶m̶a̶c̶h̶i̶n̶e̶ animation lasts. The whole reason we’re here is that we need an object to tell us how far along we are in a single rotation. By default, AnimationController “emits” values from 0.0 to 1.0. How many and how granular those values are depends on how long we want a single rotation to take. Fortunately, Dart gives us a Duration class to use. For the sake of this demo, we should have the galaxy spinning somewhere between 5 seconds and 230 million years per rotation. How about 15 seconds per turn then?

_animationController = AnimationController(
  duration: Duration(seconds: 15),
  // TODO: finish constructing me.
);

下一个必需参数是 vsync。 如果你从未来来到这里,欢迎回来! 我们希望您已经了解有关 vsync 的一切。 对于那些从过去来到这里的人,我们只想说这就是 Flutter 对对象的引用以通知更改的原因。 就是这样,它需要混入一些ticker provider 代码。 未来的帖子将深入探讨有关 vsync 和ticker providers的更多细节。

The next required parameter is vsync. If you’re here from the future, welcome back! We hope you already know everything about vsync. For those who came here from the past, we’ll just say that this is what gives Flutter a reference to the object to notify about changes. this is that thing, and it needs to mix in some ticker provider code. A future post will dive into more detail about vsync and ticker providers.

class _TimeMachineState extends State<TimeMachine> 
with SingleTickerProviderStateMixin {
  AnimationController _animationController;
 
  @override
  void initState() {
    super.initState();
    
    _animationController = AnimationController(
      duration: Duration(seconds: 15),
      vsync: this,
    );
  }
  
  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
}

如果我们把事情留在那里,什么都不会发生。 那是因为我们得到了一个控制器,但没有按下它的任何按钮! 我们希望我们的星系永远旋转,对吧? 为此,我们只会要求控制器不断重复动画。

If we left things at that, nothing much happens. That’s because we’ve been given a controller, but haven’t pushed any of its buttons! We want our galaxy to spin forever, right? For that, we’ll just ask the controller to continually repeat the animation.

_animationController = AnimationController(
  duration: Duration(seconds: 15),
  vsync: this,
)..repeat();

最后,我们可以返回并替换我们留下的空值,方法是将动画控制器传递给我们的 RotationTransition 中的 turn 参数。

Finally, we can go back and replace that null we left lingering around, by passing the animation controller to the turns parameter in our RotationTransition.

而且,尽管我们现在拥有一个无限旋转的星系,但这仍然感觉不到我们可以控制时间。 银河系现在就做它的事,对吧? 不过不要忘记,我们有一个控制器的句柄。 让我们好好利用它。²

And, although we now have an infinitely rotating galaxy, this still doesn’t quite feel like we have control of time. The galaxy just does its thing now, right? Don’t forget, though, we have a handle on a controller. Let’s make use of it.²

使用 AnimationController

Making use of an AnimationController

允许任何人控制银河系似乎有点过于宽容,所以我要把它做成一个复活节彩蛋。 我将向星系添加一个兄弟姐妹,它是一个隐藏在角落里的简单按钮,我会将它的引用传递给我们的控制器,以便在它的 onTap 侦听器中,我们可以停止或重新启动动画。

Allowing anyone to control the galaxy seems a bit too permissive though, so I’m going to make it an easter egg. I’ll add a sibling to the galaxy that’s a simple button, hidden off in the corner, and I’ll pass it a reference to our controller, so that within its onTap listener, we can stop or restart the animation.

Stack(
  children: <Widget>[
    Align(
      alignment: Alignment.bottomLeft,
      child: TimeStopper(
        controller: _animationController,
      ),
    ),
    Align(
      alignment: Alignment.center,
      child: RotationTransition(
        child: GalaxyFitz(),        
        alignment: Alignment.center,
        turns: _animationController,
      ),
    ),
  ],
)

控制器维护——除其他外——动画的状态,如果我们正在运行,我们可以检查并停止,或者如果我们不在,则重新启动。 而且,你去! 通过使用动画控制器,我们能够按需控制动画。 但这并不是您可以使用控制器做的全部。

The controller maintains — among other things — the status of the animation, which we can check and stop if we’re running or restart if we’re not. And, there you go! By using an animation controller, we’re able to control the animation on demand. But that’s not all you can do with the controller.

class TimeStopper extends StatelessWidget {
  final AnimationController controller;
 
  const TimeStopper({Key key, this.controller}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        if (controller.isAnimating) {
          controller.stop();
        } else {
          controller.repeat();
        }
      },
      child: InvisibleBox(size: 100),
    );
  }
}

使用它,还可以设置特定值的动画(或从该值向后),以给定速度向前播放动画,或使用同一控制器控制多个动画。

With it, you can also animate to (or backwards from) a specific value, fling the animation forward with a given velocity, or control multiple animations with the same controller.

在这里插入图片描述
让你的星系远离不需要的火箭。

Keeping your galaxy clean of unwanted rockets.

这只是我们第一次体验 Flutter 中的显式动画。 我们看到了 Transition 小部件如何与 AnimationController 一起工作,以提供对动画工作方式的一些方向性和控制。 在以后的帖子中,我们将深入研究显式动画以及如何获得更多自定义。

This was just our first taste of explicit animations in Flutter. We saw how a Transition widget works with AnimationController, to provide some directionality and control over how our animation works. In future posts, we’ll be diving deeper into explicit animations and how to get even more customized.

在这里插入图片描述
当星系停止时,一切都停止了

When the galaxy stops, everything stops

  1. 银河滴答声很难听到,但 AnimationController 和 TickerProviders 提供了帮助。

    Galactic ticks are hard to hear, but AnimationController and TickerProviders help.

  2. 与所有时间控制器一样:负责任地使用。

As with all of your time controllers: use responsibly.

本系列文章:

Articles in this series:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值