深入 Animation

去年,我录制了 Flutter Animations 系列中的一集,我想我会为那些更喜欢文本而不是视频的人发布相同的内容。

Last year, I got to record one of the episodes in the Flutter Animations series, and I thought I’d publish the same content for those who prefer text over video.

Animation deep dive

在该系列的其他几集中,我的同事们讨论了在 Flutter 中构建动画的所有实用方法。 在我的剧集中不是这样。 在这里,您将学习如何以最不实用的方式实现动画。 (但是,您也会在此过程中学到一些东西。)

In the other episodes of the series, my colleagues talk about all the practical ways to build animations in Flutter. Not so in my episode. Here, you’ll learn how to implement animations in the least pragmatic way imaginable. (But, you’ll also learn some things along the way.)

让我们从简单而轻松的事情开始:

Let’s start with something simple and lighthearted:

什么是运动,真的吗?

What is motion, really?

你看,运动是一种错觉。看看这个:

You see, motion is an illusion. Look at this:

在这里插入图片描述

菲利普挥手的视频。

A video of Filip waving his hand.

这是一个谎言。 您实际看到的是快速连续显示的许多静止图像。 这就是电影的运作方式。 单张图片在电影中被称为帧——因为数字屏幕的工作原理类似——它们在这里也被称为帧。 电影院通常每秒显示 24 帧。 现代数字设备每秒显示 60 到 120 帧。

It’s a lie. What you’re actually seeing are many still images shown in quick succession. This is how movies work. The individual pictures are called frames in cinema— and because digital screens work similarly— they’re called frames here too. Cinema normally shows 24 frames per second. Modern digital devices show 60 to 120 frames per second.

那么,如果运动是谎言,那么这些 AnimationFoo 和 FooTransition 小部件到底在做什么呢? 当然,由于帧需要每秒构建 120 次,因此无法每次都重新构建 UI。

So, if motion is a lie, what are all these AnimationFoo and FooTransition widgets really doing? Surely, because the frames need to be constructed up to 120 times per second, the UI cannot be rebuilt every time.

或者,可以吗?

Or, can it?

事实上,Flutter 中的动画只是在每一帧上重建部件树的一种方式。 没有特殊情况。 Flutter 的速度足以做到这一点。

In fact, animations in Flutter are just a way to rebuild parts of your widget tree on every frame. There is no special case. Flutter is fast enough to do that.

让我们看看 Flutter 动画的构建块之一:AnimatedBuilder。 这个小部件是一个 AnimatedWidget,它由 _AnimatedState 支持。 在 State 的 initState() 方法中,我们正在监听 Animation(或 Listenable,因为它在这里被调用),当它改变它的值时,我们……调用 setState()

Let’s look at one of the building blocks of Flutter animations: AnimatedBuilder. This widget is an AnimatedWidget, which is backed by _AnimatedState. In the State’s initState() method, we are listening on the Animation (or Listenable, as it is called here), and when it changes its value, we … call setState().

在这里插入图片描述

这个令人困惑的截屏视频只是表明我在上一段中说的是实话。 Animated Builder 确实会在每一帧上调用 setState()

This confusing screencast is just showing that I am telling the truth in the previous paragraph. Animated Builder really does call setState() on every frame.

就是这样。 Flutter 中的动画只是快速连续更改某些小部件的状态,每秒 60 到 120 次。

There you go. Animations in Flutter are just a quick succession of changing the state of some widget, 60 to 120 times per second.

我可以证明。这是一个从零到光速的动画。虽然它改变了每一帧的文字,但从弗利特的角度来看,它只是另一个动画。

I can prove it. Here’s an animation that “animates” from zero to the speed of light. Although it’s changing the text on every frame, from Flutter’s perspective, it’s just another animation.

在这里插入图片描述

让我们使用Flutter的动画框架来创建动画。

Let’s use Flutter’s animation framework to build that animation from first principles.

通常,我们会使用 TweenAnimationBuilder 小部件或类似的东西,但在本文中,我们将忽略所有这些,并使用 ticker、controller 和 setState。

Normally, we would use the TweenAnimationBuilder widget or something similar, but in this article, we’ll ignore all that, and go with a ticker, a controller, and setState.

Ticker

Ticker

让我们先谈谈 Ticker。 99% 的情况下,您不会直接使用Ticker。 但是,我认为谈论它仍然有帮助——即使只是为了揭开它的神秘面纱。

Let’s talk about Ticker first. 99% of the time, you won’t use a ticker directly. But, I think it’s still helpful to talk about it — even if only to demystify it.

ticker是为每一帧调用一个函数的对象。

A ticker is an object that calls a function for every frame.

var ticker = Ticker((elapsed) => print('hello'));
ticker.start();

在这种情况下,我们每帧都打印“hello”。 诚然,这不是很有用。

In this case, we’re printing ‘hello’ every frame. Admittedly, that’s not very useful.

此外,我们忘记调用ticker.dispose(),所以现在我们的ticker 将永远运行,直到我们杀死应用程序。

Also, we forgot to call ticker.dispose(), so now our ticker will go on forever, until we kill the app.

这就是 Flutter 为您提供 SingleTickerProviderStateMixin 的原因,这是您在之前的一些视频中看到的恰当命名的 mixin。

That’s why Flutter gives you SingleTickerProviderStateMixin, the aptly named mixin you’ve seen in some of the previous videos.

这个 mixin 解决了管理一个 ticker 的麻烦。 只需将它贴在您的小部件的状态上,现在您的状态就是一个 TickerProvider

This mixin takes care of the hassle of managing a ticker. Just slap it onto your widget’s state and now your state is secretly a TickerProvider

class _MyWidgetState extends State<MyWidget> 
    with SingleTickerProviderStateMixin<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

这意味着 Flutter 框架可以向您的状态请求一个 ticker。 最重要的是,AnimationController 可以向状态请求一个ticker。

What this means is that the Flutter framework can ask your state for a ticker. Most important, AnimationController can ask the state for a ticker.

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
  }
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

AnimationController 需要一个ticker才能运行。 如果你使用 SingleTickerProviderStateMixin 或者它的表亲 TickerProviderStateMixin,你可以把它提供给 AnimationController,你就完成了。

AnimationController needs a ticker for it to function. If you use SingleTickerProviderStateMixin or its cousin TickerProviderStateMixin, you can just give this to the AnimationController, and you’re done.

AnimationController

AnimationController

AnimationController 通常用于播放、暂停、反转和停止动画。 AnimationController 不是纯粹的“tick”事件,而是随时告诉我们我们处于动画的哪个点。 例如,我们已经走到一半了吗? 我们已经做到99%了吗? 我们完成了动画吗?

AnimationController is what you normally use to play, pause, reverse, and stop animations. Instead of pure “tick” events, AnimationController tells us at which point of the animation we are, at any time. For example, are we halfway there? Are we 99% there? Have we completed the animation?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xt3sMa3d-1634094192489)(https://miro.medium.com/max/250/1*nKjFR7DVd-2r7_sSgBprfA.gif#pic_center)]

通常,您使用 AnimationController,也许可以使用 Curve 对其进行变换,将其置于 Tween 中,然后在 FadeTransitionTweenAnimationBuilder 等方便的小部件之一中使用它。 但是,出于教学目的,我们不要这样做。 相反,我们将直接调用 setState

Normally, you take the AnimationController, maybe transform it with a Curve, put it through a Tween, and use it in one of the handy widgets like FadeTransition or TweenAnimationBuilder. But, for educational purposes, let’s not do that. Instead, we will directly call setState.

setState

setState

在我们初始化 AnimationController 之后,我们可以给它添加一个监听器。 并且,在那个监听器中,我们调用 setState

After we initialize the AnimationController, we can add a listener to it. And, in that listener, we call setState.

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
    _controller.addListener(_update);
  }
  void _update() {
    setState(() {
      // TODO
    });
  }
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

现在,我们可能应该设置一个状态。 让我们用一个整数来保持简单。 并且不要忘记在我们的 build 方法中实际使用状态,并根据控制器的当前值更改我们的监听器中的状态。

Now, we should probably have a state to set. Let’s keep it simple with an integer. And let’s not forget to actually use the state in our build method, and to change the state in our listener according to the current value of the controller.

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;
  int i = 0;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
    _controller.addListener(_update);
  }
  void _update() {
    setState(() {
      i = (_controller.value * 299792458).round();
    });
  }
  @override
  Widget build(BuildContext context) {
    return Text('$i m/s');
  }
}

此代码根据动画的进度从零到光速分配一个值。

This code assigns a value from zero to the speed of light depending on the animation’s progress.

运行动画

Running the animation

现在,我们只需要告诉动画需要多长时间才能完成,然后开始动画。

Now, we just need to tell the animation how long it should take to complete, and start the animation.

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;
  int i = 0;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this, 
      duration: const Duration(seconds: 1),
    );
    _controller.addListener(_update);
    _controller.forward();
  }
  void _update() {
    setState(() {
      i = (_controller.value * 299792458).round();
    });
  }
  @override
  Widget build(BuildContext context) {
    return Text('$i m/s');
  }
}

小部件在添加到屏幕后立即进行动画处理。 它在一秒钟内从零“动画”到光速。

The widget animates as soon as it’s added to the screen. And it “animates” from zero to the speed of light in a second.

在这里插入图片描述

处置控制器

Disposing of the controller

哦,别忘了处理 AnimationController。 否则,您的应用程序会出现内存泄漏。

Oh, and don’t forget to dispose of the AnimationController. Otherwise you have a memory leak in your app.

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;
  int i = 0;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this, 
      duration: const Duration(seconds: 1),
    );
    _controller.addListener(_update);
    _controller.forward();
  }
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  void _update() {
    setState(() {
      i = (_controller.value * 299792458).round();
    });
  }
  @override
  Widget build(BuildContext context) {
    return Text('$i m/s');
  }
}

也许只需要使用一个内置的小部件?

Just use a built-in widget, maybe?

正如你所看到的,全靠自己做并不好。同样的功能可以用TweenAnimationBuilder来实现,只需要更少的代码行,而且不需要使用AnimationController和调用setState

As you can see, doing it all by yourself is not great. The same functionality can be achieved with the TweenAnimationBuilder in much fewer lines of code, and without having to juggle an AnimationController and calling setState.

class MyPragmaticWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TweenAnimationBuilder(
      tween: IntTween(begin: 0, end: 299792458),
      duration: const Duration(seconds: 1),
      builder: (BuildContext context, int i, Widget child) {
        return Text('$i m/s');
      },
    );
  }
}

概括

Summary

我们看到了 Ticker 的真正含义。 我们看到了如何手动监听 AnimationController。 而且,我们看到,在基本层面上,动画只是快速、连续地重建小部件。 你可以在任何框架上做任何你想做的事情。

We saw what Ticker really is. We saw how to manually listen to an AnimationController. And, we saw that, at the basic level, animations are just fast, consecutive rebuilds of a widget. You can do whatever you want on any frame.

本系列文章:

Articles in this series:

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值