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

2,359 阅读7分钟

为什么要使用TweenAnimationBuilder?假设您要创建一个基本的动画:一个不会永远重复的动画,它只是一个widget或widget 树。Flutter有一个约定为其隐式动画窗口小部件AnimatedFoo,此处Foo是动画属性的名称。不相信我吗?下面是内置的,隐式动画控件的一个示例:

AnimatedContainer,``AnimatedCrossFade,``AnimatedDefaultTextStyle, ``AnimatedModalBarrier,AnimatedOpacity,AnimatedPadding,AnimatedPhysicalModel,AnimatedPositioned,AnimatedPositionedDirectional,AnimatedSwitcher。

这组小部件功能强大,您可以使用它们来满足很多需求。AnimatedContainer甚至可以让您设置渐变动画和旋转小部件,而无需担心AnimationController!

但是,如果您需要创建基本动画,而您所要查找的不是内置的隐式动画,您仍然可以使用

TweenAnimationBuilder!来创建该动画。

基础

要使用TweenAnimationBuilder,使用duration参数设置了我希望动画使用的时间长度,并使用...Tween参数设置了动画要设置的值的范围。正如名字所暗示的,一个Tween对象允许您指定值的范围是要动画BE吐温。

我需要指定的最后一件事是builder参数,该参数返回给定时间我的动画小部件的外观。

该构建器函数采用与您的Tween值相同类型的参数,该参数基本上告诉Flutter在给定时刻当前的动画值是什么。

///这是使用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,但是此小部件还有很多其他功能!出于说明目的,我为一个非常常见的用例创建了一个应用程序:太空中多普勒效果的说明。好的,这是一个愚蠢的用例,但是您可能想要对图像应用滤色器并为不断变化的颜色设置动画……这正是我们在这种情况下要做的。

在多普勒效应中,当恒星在太空中移离您时,光波会拉长,使光移向光谱的红色末端。这种影响非常微妙,肉眼看不到,但是天文学家用它来确定恒星和星系相对于我们的速度。

图片发布

有关更多详细信息,请咨询您当地的天体物理学家。

在我们的应用程序中,我们将使其变得更微妙。我有一个漂亮的星星图像,并且要更改其颜色,我将使用ColorFiltered小部件。

我应用了混合模式,并告诉它将橙色混合到图像中,使它略带红色。

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

下一步…动画!

没有内置的窗口小部件可将任意颜色滤镜应用于窗口小部件,但我们可以使用构建自己的窗口小部件TweenAnimationBuilder。要随时间更改颜色,我们想要修改应用于滤镜的颜色。这就是我们要赋予生命的价值。我们将ColorFiltered小部件放入的builder函数TweenAnimationBuilder

。如前所述,a Tween 只是我们要设置动画的值的范围。在这种情况下,我们将使用一个 ColorTween 在白色(好像没有滤镜)和橙色之间进行动画处理。在那里,您拥有了!一个精美的动画彩色滤光片,包含10行代码

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),
       );
    },
)

Image for post

根据你想要的动画效果,你的 Tween 还可以指定除颜色或数字以外的东西之间的范围。您可以使用带有Offset 的 Tween对象为小部件的位置变化设置动画,甚至可以为小部件的边界变化设置动画!关键是您有很多选择。

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

class SuperBasic 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<double>(
                        tween: colorTween,                        duration: Duration(seconds: 2),
                        builder: (BuildContext context, double angle, Widget child) {
                             return Transform.rotate(
                                    angle: angle,
                                    child: Image.asset('assets/Earth.png'),
                             );
                         },
                    ),
                ),
            ],
       );
    }
}

动态修改Tween值

前面的示例演示了一种非常简单的方法,无需使用setState任何内容即可将一个值动画化为另一个值。但是通过动态地修改Tween价值,你可以做更多的TweenAnimationBuilder。

class OngoingAnimationByModifyingEndTweenValue extends StatefulWidget {
    @override  
    _OngoingAnimationState createState() => _OngoingAnimationState();
} class _OngoingAnimationState extends State<OngoingAnimationByModifyingEndTweenValue> {
    double _newValue = 0.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也用作my中的最终值Tween。现在,每次我拖动滑块时动画都会更新。

图片发布

要记住的一件事是,TweenAnimationBuilder总是从当前值移动到新的最终值。这意味着当我拖动滑块时,我看到的是颜色相对于其先前颜色的变化,而不是一开始就总是从白色开始进行动画处理。只需为我设置一个新的最终值Tween,我就可以反转动画或移动到两者之间的任何点。TweenAnimationBuilder总是在其当前值和新端点之间平滑地设置动画。可以推断,这意味着动态更改起始位置Tween无效。

// DON'T DO THIS! YOU WON'T SEE AN ANIMATION IF YOU JUST UPDATE THE START VALUE!
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

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

第二个是您可以指定的回调,因此可以在动画完成时执行某些操作。也许您想在此动画结束后再显示另一个小部件。您还可以使用此回调作为来回反转动画的方法。我建议您在执行此操作之前请仔细考虑。回调使您尝试执行的动画类型不太清晰,因为值设置是通过代码分配的。由于这些值是不连续的(再次跳回开始处),因此,如果要重复播放动画,则需要某种显式动画:内置的显式动画小部件或extend AnimatedWidget。

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。设置子参数是潜在的性能优化。即使颜色发生变化,星形图像小部件本身也保持不变。正如目前所写,每次调用builder方法时,都会重新构建该图像小部件。我们可以通过将其作为子参数传递来提前构建该星形图像。这样,Flutter知道它需要从一个帧到另一个帧重建的唯一小部件是新的彩色滤光片,而不是星形图像本身。这个例子很简单,所以实际上没有明显的区别。但是,如果我们要动画一个更加复杂的组件,则可以想象性能优化可能变得更加重要。

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),
              );
            },
          ),
        ),
      ],
    );
  }
}

总结

这就是用TweenAnimationBuilder!编写自己的炫酷隐式动画所需的全部知识!回顾一下TweenAnimationBuilder如果找不到内置AnimatedFoo类型的小部件,则是创建“设置并忘记它”隐式动画的好方法。您可以使用来完成简单的动画,TweenAnimationBuilder而无需使用StatefulWidget。您可以在中更改该最终值Tween以平滑地设置为新值。还可以通过提前传入孩子或Tween在适当的时候设置静态的最终结果来优化性能。