在Flutter中,我如何更改某些小部件并使其动画成其新大小?

时间:2018-08-07 23:16:02

标签: flutter flutter-animation

我想更改某个小部件的子项,然后使其动画化为新子项的高度,同时还带有淡入淡出过渡。

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

如果我使用AnimatedSwitcher,它可以让我简单地更改其子级,但是它只会设置淡入淡出的动画效果,而不是大小。

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

我缺少一些可以满足我需要的小部件吗?如果没有,该如何不使用AnimationController s来做到这一点?

2 个答案:

答案 0 :(得分:1)

这解决了问题。它会同时淡化和设置大小的动画,而无需指定两个子对象:

import 'package:flutter/material.dart';

/// A widget that does a fade and size transition between a "new" widget and the "old "widget
/// previously set as a child.
///
/// The "old" and the "new" child must have the same width, but can have different heights.
///
/// If the "new" child is the same widget type as the "old" child, but with different parameters,
/// then [AnimatedSizeAndFade] will not do a transition between them, since as far as the framework
/// is concerned, they are the same widget, and the existing widget can be updated with the new
/// parameters. To force the transition to occur, set a [Key] (typically a [ValueKey] taking any
/// widget data that would change the visual appearance of the widget) on each child widget that
/// you wish to be considered unique.
class AnimatedSizeAndFade extends StatelessWidget {
  final Widget child;
  final TickerProvider vsync;
  final Duration fadeDuration;
  final Duration sizeDuration;
  final Curve fadeCurve;
  final Curve sizeCurve;

  const AnimatedSizeAndFade({
    this.child,
    this.vsync,
    this.fadeDuration = const Duration(milliseconds: 500),
    this.sizeDuration = const Duration(milliseconds: 500),
    this.fadeCurve = Curves.easeInOut,
    this.sizeCurve = Curves.easeInOut,
  });

  @override
  Widget build(BuildContext context) {
    return ClipPath(
      child: AnimatedSize(
        vsync: vsync,
        child: AnimatedSwitcher(
          child: child,
          duration: fadeDuration,
          layoutBuilder: _layoutBuilder,
        ),
        duration: sizeDuration,
        curve: Curves.easeInOut,
      ),
    );
  }

  Widget _layoutBuilder(Widget currentChild, List<Widget> previousChildren) {
    List<Widget> children = previousChildren;
    if (currentChild != null) {
      if (previousChildren == null || previousChildren.isEmpty)
        children = [currentChild];
      else {
        children = [
          Positioned(
            left: 0.0,
            right: 0.0,
            child: Container(
              child: previousChildren[0],
            ),
          ),
          Container(
            child: currentChild,
          ),
        ];
      }
    }
    return new Stack(
      overflow: Overflow.visible,
      children: children,
      alignment: Alignment.center,
    );
  }
}

像这样使用它:

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

答案 1 :(得分:0)

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

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