Flutter 中的自定义隐式动画……使用 TweenAnimationBuilder

要在 Flutter 中制作动画,有许多不同的选项可用。 您如何选择合适的动画小部件? 这篇文章是我们动画系列的一部分,深入探讨了 TweenAnimationBuilder 的最佳用例以及不同小部件何时最适合这项工作。 本系列的上一篇文章解释了所有内置的隐式动画小部件的强大程度以及您可以使用它们完成多少工作。

To make animations in Flutter, there are many different options available. How do you choose the right animation widget? This article, part of our animation series, delves into the best use cases for TweenAnimationBuilder versus when a different widget is best for the job. The previous article in this series explains just how powerful all the built-in implicit animation widgets are and how much you can accomplish with them.

在这里插入图片描述

用于了解在任何场景中使用什么动画的流程图。 本文重点介绍从右数第二个“结束状态”,TweenAnimationBuilder。

A flow chart for understanding what animation to use in any scenario. This article focuses on the second-from-the-right “end state”, TweenAnimationBuilder.

为什么要使用 TweenAnimationBuilder? 假设您想创建一个基本动画:一个不会永远重复的动画,它只是一个小部件或小部件树。 Flutter 有一个约定将其隐式动画小部件命名为 AnimatedFoo,其中 Foo 是动画属性的名称。 不相信我? 下面是一个内置的隐式动画小部件示例:AnimatedContainerAnimatedCrossFadeAnimatedDefaultTextStyleAnimatedModalBarrierAnimatedOpacityAnimatedPaddingAnimatedPhysicalModelAnimatedPositionedAnimatedPositionedDirectionalAnimatedSwitcher。 这组小部件非常强大,您只需使用它们就可以满足您的许多需求。 AnimatedContainer 甚至可以让您动画渐变和旋转小部件,而无需担心 AnimationController

Why use TweenAnimationBuilder? Suppose you want to create a basic animation: an animation that doesn’t repeat forever and is just one widget or widget tree. Flutter has a convention of naming its implicitly animated widgets AnimatedFoo, where Foo is the name of the property that animates. Don’t believe me? Here’s a sample of built-in, implicitly animated widgets: AnimatedContainer, AnimatedCrossFade, AnimatedDefaultTextStyle, AnimatedModalBarrier, AnimatedOpacity, AnimatedPadding, AnimatedPhysicalModel, AnimatedPositioned, AnimatedPositionedDirectional, AnimatedSwitcher. This set of widgets is impressively powerful, and you can accomplish a lot of your needs just using those. AnimatedContainer can even let you animate gradients and rotate widgets, all without needing to worry about an AnimationController!

但是,如果您需要创建一个基本动画并且这些内置隐式动画都不是您想要的,您仍然可以使用 TweenAnimationBuilder 创建该动画!

However, if you need to create a basic animation and none of those built-in implicit animations are what you’re looking for, you can still create that animation with TweenAnimationBuilder!

基础知识

The basics

要使用 TweenAnimationBuilder,我使用持续时间参数设置了我希望我的动画使用的时间长度,以及我希望它使用…Tween 参数设置动画的值范围。 顾名思义,Tween 对象使您能够指定要在其间设置动画的值范围。

To use TweenAnimationBuilder, I set the length of time that I want my animation to take with the duration parameter, and the range of values that I want it to animate between with the…Tween parameter. As the name suggests, a Tween object enables you to specify a range of values that you want to animate between.

我需要指定的最后一件事是 builder 参数,它返回我的动画小部件在给定时刻的外观。 这个构建器函数接受一个与你的 Tween 值相同类型的参数,它基本上告诉 Flutter 在给定时刻当前的动画值是什么。

The last thing I need to specify is the builder parameter, which returns what my animated widget will look like at a given moment in time. This builder function takes a parameter that is the same type as your Tween values, which basically tells Flutter what the current animation value is at a given moment.

/// This is an EXTREMELY bare-bones illustration of using TweenAnimationBuilder.
/// See the rest of the article for optimizations. 
/// Also note that this example is for illusration use only -- an implicit rotation
/// animation can be accomplished with AnimatedContainer.

/// 这是使用 TweenAnimationBuilder 的极其简单的说明。 
/// 有关优化,请参阅文章的其余部分。
/// 另请注意,此示例仅用于说明用途,
/// 可以使用 AnimatedContainer 完成隐式旋转动画。
class SuperBasic extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        starsBackground,
        Center(
          child: TweenAnimationBuilder<double>(
            tween: Tween<double>(begin: 0, end: 2 * math.pi),
            duration: Duration(seconds: 2),
            builder: (BuildContext context, double angle, Widget child) {
              return Transform.rotate(
                angle: angle,
                child: Image.asset('assets/Earth.png'),
              );
            },
          ),
        ),
      ],
    );
  }
}

深入 TweenAnimationBuilder

TweenAnimationBuilder in depth

上面的示例代码显示了使用 TweenAnimationBuilder 所需的最低限度的参数集,但是这个小部件还提供了更多! 出于说明目的,我为一个极其常见的用例创建了一个应用程序:太空中的多普勒效应的插图。 好吧,这是一个愚蠢的用例,但您可能希望将颜色过滤器应用于图像并为不断变化的颜色设置动画……这正是我们在这种情况下要做的。

The example code above showed the bare-minimum set of arguments necessary to use TweenAnimationBuilder, but there is a lot more this widget has to offer! For illustration purposes, I created an app for an extremely common use case: illustration of the Doppler effect in space. Okay, it’s a silly use case, but you might want to apply a color filter to an image and animate the changing colors…which is exactly what we’ll be doing in this scenario.

在这里插入图片描述
有关更多详细信息,请咨询您当地的天体物理学家。

Consult your local astrophysicist for more details.

在我们的应用程序中,我们将使其不那么微妙。 我有一个漂亮的星星图像,要更改它的颜色,我将使用 ColorFiltered 小部件。 我应用了一个混合模式,并告诉它把橙色混合到图像中,使它更红一点。

In our app we’re going to make this a little less subtle. I have a nice image of a star, and to change its color, I’m going to use the ColorFiltered widget. I apply a blend mode, and tell it to blend orange into the image to make it a little more reddish.

ColorFiltered(
  child: Image.asset('assets/sun.png'),
  colorFilter: ColorFilter.mode(color, BlendMode.modulate),
)

下一步……动画! 没有内置小部件可以将任意颜色过滤器应用于小部件,但我们可以使用 TweenAnimationBuilder 自己构建一个。 为了随着时间的推移改变颜色,我们想要修改我们应用到过滤器的颜色。 所以这就是我们要制作动画的价值。 我们将把 ColorFiltered 小部件放在 TweenAnimationBuilder 的构建器函数中。 正如我之前提到的,Tween 只是我们在其间设置动画的值范围。 在这种情况下,我们将使用 ColorTween 在白色和橙色之间设置动画,就像我们没有过滤器一样。 你有它! 一个用 10 行代码制作的漂亮的动画颜色过滤器。

Next step…animation! There isn’t a built-in widget that applies an arbitrary color filter to a widget, but we can build one ourselves with TweenAnimationBuilder. To change the color over time, we want to modify the color that we’re applying to the filter. So that’s the value that we’ll animate. We’ll put the ColorFiltered widget inside the builder function of the TweenAnimationBuilder. As I mentioned before, a Tween is just the range of values that we are animating between. In this case, we’ll use a ColorTween to animate between white, which is as if we had no filter, and orange. And there you have it! A nicely animated color filter in 10 lines of code.

TweenAnimationBuilder(
  tween: ColorTween(begin: Colors.white, end: Colors.red),
  duration: Duration(seconds: 2),
  builder: (_, Color color, __) {
    return ColorFiltered(
      child: Image.asset('assets/sun.png'),
      colorFilter: ColorFilter.mode(color, BlendMode.modulate),
    );
  },
)

在这里插入图片描述

不过,根据您想要设置动画的内容,您的 Tween 可以指定颜色或数字以外的事物之间的范围。 您可以使用带有 Offset 对象的 Tween 来为小部件位置的变化设置动画,或者您甚至可以为小部件的边框如何变化设置动画! 关键是你有很多选择。

Depending on what you want to animate though, your Tween can specify ranges between things other than colors or numbers. You can have a Tween with Offset objects to animate the change of a widget’s position, or you can even animate how the border of a widget changes! The point is you have a ton of options.

Tween 是可变的,所以如果你知道你总是要在同一组值之间设置动画,最好在你的类中将你的 Tween 声明为静态最终变量。 这样,您就不会在每次重建时都创建一个新对象。

Tweens are mutable, so if you know that you’re always going to animate between the same set of values, it’s best to declare your Tween as a static final variable in your class. That way, you don’t create a new object every time you rebuild.

class ColorAnimationWithStaticFinal extends StatelessWidget {
  static final colorTween = ColorTween(begin: Colors.white, end: Colors.red);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        starsBackground,
        Center(
          child: TweenAnimationBuilder<Color>(
            tween: colorTween,
            duration: Duration(seconds: 2),
            builder: (_, Color color, __) {
              return ColorFiltered(
                child: Image.asset('assets/sun.png'),
                colorFilter: ColorFilter.mode(color, BlendMode.modulate),
              );
            },
          ),
        ),
      ],
    );
  }
}

动态修改 Tween 值

Dynamically modifying Tween values

前面的例子展示了一种非常简单的方法,可以在不使用 setState 或任何东西的情况下从一个值到另一个值进行动画处理。 但是,通过动态修改 Tween 值,您可以使用 TweenAnimationBuilder 做更多事情。

The previous example showed a really simple way to animate from one value to another without using setState or anything. But, you can do more with TweenAnimationBuilder by dynamically modifying your Tween value.

class OngoingAnimationByModifyingEndTweenValue extends StatefulWidget {
  @override
  _OngoingAnimationState createState() => _OngoingAnimationState();
}

class _OngoingAnimationState extends State<OngoingAnimationByModifyingEndTweenValue> {
  double _newValue = .4;
  Color _newColor = Colors.white;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        starsBackground,
        Column(
          children: <Widget>[
            Center(
              child: TweenAnimationBuilder(
                tween: ColorTween(begin: Colors.white, end: _newColor),
                duration: Duration(seconds: 2),
                builder: (_, Color color, __) {
                  return ColorFiltered(
                    child: Image.asset('assets/sun.png'),
                    colorFilter: ColorFilter.mode(color, BlendMode.modulate),
                  );
                },
              ),
            ),
            Slider.adaptive(
              value: _newValue,
              onChanged: (double value) {
                setState(() {
                  _newValue = value;
                  _newColor = Color.lerp(Colors.white, Colors.red, value);
                });
              },
            ),
          ],
        ),
      ],
    );
  }
}

我将代码更改为还包含一个 Slider 小部件。 然后我声明了一个名为 _newColor 的局部变量,它接受滑块值并将其转换为颜色。 _newColor 也用作我的 Tween 中的结束值。 现在每次拖动滑块时动画都会更新。

I changed the code to also include a Slider widget. Then I declared a local variable called _newColor that takes the slider value and converts it to a color. _newColor is also used as the end value in my Tween. Now the animation updates every time I drag the slider.

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

要记住的一件事是 TweenAnimationBuilder 总是从当前值移动到新的结束值。 这意味着当我拖动滑块时,我会看到颜色相对于其先前颜色的变化,而不是一开始总是从白色开始动画。 只需为我的 Tween 设置一个新的结束值,我就可以反转我的动画或移动到两者之间的任何点。 TweenAnimationBuilder 始终在其当前值和新端点之间平滑地设置动画。 正如您可能推断的那样,这意味着动态更改 Tween 的开头没有任何效果。

One thing to keep in mind is that TweenAnimationBuilder always moves from the current value to the new end value. That means as I drag the slider, I see the color change relative to its previous color, rather than always animating from white at the very beginning. Just by setting a new end value to my Tween, I can reverse my animation or move to any point in between. TweenAnimationBuilder always smoothly animates between its current value and the new end point. As you can perhaps infer, this means dynamically changing the start of your Tween has no effect.

// DON'T DO THIS! YOU WON'T SEE AN ANIMATION IF YOU JUST UPDATE THE START VALUE!  
// 不要这样做! 如果您只是更新开始值,您将看不到动画!
// 本案例中是修改ColorTween 的 begin 值, 不会看到动画!
// begin 值只在第一次动画时有效, 后面每次执行动画都是
// 从上一个 end 值到当前 end 值, 所以需要再次执行动画
// 时只需要修改 end 值
class NopeNopeNope extends StatefulWidget {
  @override
  _NopeNopeNopeState createState() => _NopeNopeNopeState();
}

class _NopeNopeNopeState extends State<NopeNopeNope> {
  double _newValue = .4;
  Color _newColor = Colors.white;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        starsBackground,
        Column(
          children: <Widget>[
            Center(
              child: TweenAnimationBuilder(
                tween: ColorTween(begin: _newColor, end: Colors.red),
                duration: Duration(seconds: 2),
                builder: (_, Color color, __) {
                  return ColorFiltered(
                    child: Image.asset('assets/sun.png'),
                    colorFilter: ColorFilter.mode(color, BlendMode.modulate),
                  );
                },
              ),
            ),
            Slider.adaptive(
              value: _newValue,
              onChanged: (double value) {
                setState(() {
                  _newValue = value;
                  _newColor = Color.lerp(Colors.white, Colors.red, value);
                });
              },
            ),
          ],
        ),
      ],
    );
  }
}

onEnd 和 child

onEnd and child

还有一些我还没有谈到的其他参数。 第一个是curve,用于描述我们应该如何在 Tween 范围内的开始值和结束值之间转换。 在上一篇文章中,我们讨论了如何创建自定义curve,但也有很多很棒的预定义选项。

There are a few other parameters that I haven’t talked about yet. The first one is the curve, to describe how we should transition between the beginning and end values in our Tween range. In the previous article we talked about how you can even create a custom curve, but there are a lot of great predefined options too.

第二个是您可以指定的回调,以便在动画完成时执行某些操作。也许您希望在动画完成后显示另一个小部件。您还可以使用此回调来来回反转动画。但我建议你在做这件事之前要仔细考虑。回调使您尝试执行的动画类型不那么清晰,因为值设置是通过代码分发的。因为这些值是不连续的(再次跳回到起始位置),如果想要重复动画,则需要某种显式动画:内置的显式动画小部件或扩展AnimatedWidget

The second one is a callback you can specify, so you can do something when the animation completes. Perhaps you want to make another widget appear after this animation finishes. You can also use this callback as a way to reverse your animation back and forth. I recommend that you think carefully before doing this though. The callback makes the type of animation you’re trying to do less clear because the value setting is distributed through your code. Because the values are discontinuous (jumping back to the start again), if you want a repeating animation you’ll need some sort of explicit animation: either a built-in explicit animated widget or extending AnimatedWidget.

class BackAndForth extends StatefulWidget {
  @override
  _BackAndForthState createState() => _BackAndForthState();
}

class _BackAndForthState extends State<MyHomePage> {
  Color _newColor = Colors.red;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        starsBackground,
        Center(
          child: TweenAnimationBuilder(
            tween: ColorTween(begin: Colors.white, end: _newColor),
            duration: Duration(seconds: 2),
            onEnd: () {
              setState(() {
                _newColor = _newColor == Colors.red ? Colors.white : Colors.red;
              });
            },
            builder: (_, Color color, __) {
              return ColorFiltered(
                child: Image.asset('assets/sun.png'),
                colorFilter: ColorFilter.mode(color, BlendMode.modulate),
              );
            },
          ),
        )
      ],
    );
  }
}

在这里插入图片描述

我们还没有讨论最后一个参数:child参数。 设置 child 参数是一种潜在的性能优化。 即使颜色发生变化,星形图像小部件本身也保持不变。 但是,正如当前编写的那样,每次调用构建器方法时都会重建该图像小部件。 我们可以通过将其作为子参数传入来提前构建该星形图像。 这样,Flutter 知道它需要逐帧重建的唯一小部件是新的滤色器,而不是星形图像本身。 这个例子很简单,所以实际上没有明显的区别。 但是,如果我们为一个更复杂的组件制作动画,您可以想象性能优化可能会变得更加重要。

There’s one last parameter we haven’t discussed yet: the child parameter. Setting the child parameter is a potential performance optimization. Even though the color changes, the star image widget itself stays the same. As it’s currently written though, that image widget gets reconstructed every time that builder method gets called. We can build that star image ahead of time by passing it in as a child parameter. This way, Flutter knows the only widget that it needs to rebuild from frame to frame is the new color filter, not the star image itself. This example is simple so there’s really no noticeable difference. But, if we were animating a much more complex component, you can imagine performance optimization might become more important.

class ChildParameter extends StatelessWidget {
  static final colorTween = ColorTween(begin: Colors.white, end: Colors.red);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        starsBackground,
        Center(
          child: TweenAnimationBuilder<Color>(
            tween: colorTween,
            child: Image.asset('assets/sun.png'),
            duration: Duration(seconds: 2),
            builder: (_, Color color, Widget myChild) {
              return ColorFiltered(
                child: myChild,
                colorFilter: ColorFilter.mode(color, BlendMode.modulate),
              );
            },
          ),
        ),
      ],
    );
  }
}

概括

Summary

这就是使用 TweenAnimationBuilder 编写自己很酷隐式动画所需的全部知识! 总结一下,如果您找不到内置的 AnimatedFoo 类型小部件,TweenAnimationBuilder 是创建“设置并忘记”隐式动画的好方法。 您可以使用 TweenAnimationBuilder 完成简单的动画,而无需使用 StatefulWidget。 您可以在 Tween 中更改该结束值以平滑地将动画设置为新值。 通过提前传入child或在适当时设置static final Tween,也有潜在的性能优化。 要了解有关您应该在何时使用哪个动画小部件的更多信息,请查看本系列中的其他文章。

That’s all you need to know to write your own cool implicit animations with TweenAnimationBuilder! To recap, TweenAnimationBuilder is a great way to create “set-it-and-forget-it” implicit animations if you can’t find a built-in AnimatedFoo type widget. You can accomplish simple animations using TweenAnimationBuilder without needing to use a StatefulWidget. You can change that end value in the Tween to smoothly animate to a new value. There are also potential performance optimizations by passing in a child ahead of time or setting a static final Tween when appropriate. To learn more about which animation widget you should use when, check out the other articles in this series.

这是以下视频内容的文章版本。 如果您更喜欢视频,请注意:

This is the article version of the following video content. If you prefer videos, watch away:

Creating your own Custom Implicit Animations with TweenAnimationBuilder

本系列文章:

Articles in this series:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值