在 Flutter 中,我怎样才能更改一些小部件并看到它的动画到它的新大小?

Posted

技术标签:

【中文标题】在 Flutter 中,我怎样才能更改一些小部件并看到它的动画到它的新大小?【英文标题】:In Flutter, how can I change some widget and see it animate to its new size? 【发布时间】:2019-01-15 03:03:09 【问题描述】:

我想更改某个小部件的子级,然后将其动画到新子级的高度,并带有淡入淡出过渡。

我可以用AnimatedCrossFade 做到这一点,但我必须同时保留firstChildsecondChild,这是我做不到的。

如果我使用AnimatedSwitcher,它可以让我简单地改变它的孩子,但它只会动画淡化,而不是大小。

AnimatedContainer 也不起作用,因为我事先不知道孩子的大小。

是否有一些我缺少的小部件可以满足我的需要,如果没有,我该如何在不诉诸 AnimationControllers 的情况下做到这一点?

【问题讨论】:

你试过AnimatedSize吗?它不会褪色,但在其他方面完全符合您的要求。我还在寻找一个可以同时淡化和动画大小的小部件,而不必指定两个孩子...... @boformer 嗯,我想我错过了这门课,因为它不在implicit_animations.dart 文件中。如果您将此评论作为答案,我会支持它,因为它很有用,尽管它不完全是我想要的。 “不借助 AnimationController”是什么意思?这是动画最基本的必需组件 @RémiRousselet 在内部,是的,我的意思是不必在调用代码中明确定义控制器,仅此而已。我已经找到了解决方案并在下面发布。 【参考方案1】:

这解决了问题:

https://pub.dev/packages/animated_size_and_fade

它同时淡化和动画大小,无需指定两个孩子。您还可以分别为淡入淡出和大小定义持续时间和曲线。

像这样使用它:

bool toggle=true; 
Widget widget1 = ...;
Widget widget2 = ...;

AnimatedSizeAndFade(
    vsync: this,
    child: toggle ? widget1 : widget2,
),

注意:如果你想使用上面的代码,请阅读文档:

“旧”和“新”子级必须具有相同的宽度,但可以具有不同的高度。

如果“新”子项与“旧”子项的小部件类型相同,但参数不同,则AnimatedSizeAndFade 不会在它们之间进行转换,因为就框架而言,它们是相同的小部件,并且可以使用新参数更新现有小部件。要强制进行转换,请设置 Key(通常是 ValueKey,获取任何会改变您希望被视为唯一的每个子小部件上小部件的视觉外观的小部件数据。


这是一个可运行的示例:

import 'package:flutter/material.dart';
import 'package:widgets/widgets.dart';

void main() 
  runApp(MyApp());


class MyApp extends StatefulWidget 
  @override
  _MyAppState createState() => _MyAppState();


class _MyAppState extends State<MyApp> with TickerProviderStateMixin 
  bool toggle;

  @override
  void initState() 
    super.initState();
    toggle = false;
  

  @override
  Widget build(BuildContext context) 
    var toggleButton = Padding(
      padding: const EdgeInsets.all(8.0),
      child: MaterialButton(
        child: const Text("Toggle"),
        color: Colors.grey,
        onPressed: () 
          setState(() 
            toggle = !toggle;
          );
        ,
      ),
    );

    var widget1 = Container(
      key: ValueKey("first"),
      color: Colors.blue,
      width: 200.0,
      child: const Text(
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt "
            "ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
            "ullamco laboris nisi ut aliquip ex ea commodo consequat.",
      ),
    );

    var widget2 = Container(
      key: ValueKey("second"),
      color: Colors.red,
      width: 200.0,
      child: const Text(
        "I am ready for my closeup.",
      ),
    );

    return MaterialApp(
      home: Material(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(height: 100.0),
            toggleButton,
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text("Some text above."),
                AnimatedSizeAndFade(
                  vsync: this,
                  child: toggle ? widget1 : widget2,
                  fadeDuration: const Duration(milliseconds: 300),
                  sizeDuration: const Duration(milliseconds: 600),
                ),
                const Text("Some text below."),
              ],
            ),
          ],
        ),
      ),
    );
  

【讨论】:

这不起作用。我通过在 40x60 红色容器和 60x40 绿色容器之间切换来测试它;并且没有淡入淡出,尺寸过渡无效 @RémiRousselet 请阅读文档:“旧”和“新”子级必须具有相同的宽度,但可以具有不同的高度。此外,如果子小部件的类型相同,则必须定义不同的键。 我认为您可以放心地接受自己的答案;) 挑剔:最好使用ValueKey 而不是GlobalKey,例如ValueKey('first')ValueKey('second') 考虑在答案中提取您的文档。这种方式更具可读性。 @boformer 和 Remi:按照他们的建议做了。【参考方案2】:

有很多方法可以实现这一目标。这只是一个例子:

  class LogoApp extends StatefulWidget 
    _LogoAppState createState() => _LogoAppState();
  

  class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin 
    Animation animation;
    Animation animationOpacity;
    AnimationController controller;

    initState() 
      super.initState();
      controller = AnimationController(
          duration: const Duration(milliseconds: 2000), vsync: this);
      final CurvedAnimation curve =
          CurvedAnimation(parent: controller, curve: Curves.easeIn);
      animation = Tween(begin: 0.0, end: 300.0).animate(curve);
      animationOpacity = Tween(begin: 0.0, end: 1.0).animate(curve);
      controller.forward();
    

    Widget build(BuildContext context) 
      return AnimatedBuilder(
        animation: controller,
        builder: (context, widget) 
          return Opacity(
            opacity: animationOpacity.value,
                    child: Container(
              margin: EdgeInsets.symmetric(vertical: 10.0),
              height: animation.value,
              width: animation.value,
              child: FlutterLogo(),
            ),
          );
        ,
      );
    

    dispose() 
      controller.dispose();
      super.dispose();
    
  

用法:

   @override
    Widget build(BuildContext context) 
      return new MaterialApp(
          title: 'Flutter Demo',
          theme: new ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: Material(child: Center(child: LogoApp())));
    

请参阅此文档Flutter Animations

更新

  class LogoApp extends StatefulWidget 
    _LogoAppState createState() => _LogoAppState();
  

  class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin 
    Animation controllerAnimation;
    AnimationController controller;

    initState() 
      super.initState();
      controller = AnimationController(
          duration: const Duration(milliseconds: 1000), vsync: this);
      final CurvedAnimation curve =
          CurvedAnimation(parent: controller, curve: Curves.easeIn);
      controllerAnimation = Tween(begin: 0.0, end: 1.0).animate(curve);
      controller.forward();
    

    bool isSelected = false;

    Widget build(BuildContext context) 
      return Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          AnimatedSwitcher(
            duration: Duration(seconds: 10),//it is ignored
            child: isSelected
                ? Container(
                    width: 200.0,
                    height: 200.0,
                    child: FlutterLogo(
                      colors: Colors.red,
                    ),
                  )
                : Container(
                    width: 200.0,
                    height: 200.0,
                    child: FlutterLogo(
                      colors: Colors.blue,
                    )),
            transitionBuilder: defaultTransitionBuilder,
          ),
          MaterialButton(
            child: Text("Texting"),
            onPressed: () 
              if (controller.isCompleted) 
                controller.reset();
              

              controller.forward();

              setState(() 
                isSelected = !isSelected;
              );
            ,
          )
        ],
      );
    

    Widget defaultTransitionBuilder(Widget child, Animation<double> animation) 
      return AnimatedBuilder(
        animation: controller,
        builder: (context, widget) 
          return Opacity(
            opacity: controllerAnimation.value,
            child: ScaleTransition(
              scale: controllerAnimation,
              child: widget,
            ),
          );
        ,
        child: child,
      );
    

    dispose() 
      controller.dispose();
      super.dispose();
    
  

【讨论】:

这不是我想要的。正如我所说,我事先不知道孩子们的大小。另外,我想将一个小部件淡入另一个小部件,而不是转换小部件的不透明度。 你有一些关于你想要什么的视频/gif吗?你试过用英雄动画吗? 不,我没有。当我改变一个孩子时,我只想转换大小和淡出。 我使用自定义更新了我的示例:transitionBuilder,它对你有用吗? 有趣的效果,但我想要的是第一个小部件的大小过渡到第二个的大小,而第一个小部件图像过渡到第二个小部件图像。你所做的是第一个小部件被删除,第二个小部件增长并淡入。

以上是关于在 Flutter 中,我怎样才能更改一些小部件并看到它的动画到它的新大小?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:故意隐藏键盘下的Stack项目

如何在 Flutter 的主小部件中为小部件设置背景颜色?

Flutter Container 在 Row 小部件内定位或对齐

更改 Flutter 中所有儿童的字体系列

Flutter:一次多次使用相同的动画小部件

三元条件没有重建我的小部件 - Flutter