9

I would like to change the child of some widget, and then see it animate to the new child's height, also with a fade transition.

I can do that with AnimatedCrossFade, but then I must keep both firstChild and secondChild, which I cannot.

If I use AnimatedSwitcher, it lets me simply change its child, but then it only animates the fade, not the size.

The AnimatedContainer also doesn't work, since I don't know the size of the children in advance.

Is there some widget I am missing that does what I need, and if not, how can I do that without resorting to AnimationControllers?

4
  • Did you try AnimatedSize? It does not fade, but otherwise does exactly what you want. I'm also still looking for a widget that fades and animates the size at the same time, without having to specify two children...
    – boformer
    Commented Aug 7, 2018 at 23:27
  • @boformer Hm, I think I missed this class since it was not in implicit_animations.dart file. If you make this comment into an answer I will upvote it, since it's useful, although it's not exactly what I want. Commented Aug 7, 2018 at 23:53
  • What do you mean by "without resorting to AnimationController"? This is the most basic required component of animations Commented Aug 8, 2018 at 2:21
  • @RémiRousselet Internally, yes, I mean without explicitly having to define a controller in the calling code, that's all. I have already found a solution and posted it below. Commented Aug 8, 2018 at 2:27

2 Answers 2

12

This solves the question:

https://pub.dev/packages/animated_size_and_fade

It fades and animates the size at the same time, without having to specify two children. You can also define a duration and curve for both the fade and the size, separately.

Use it like this:

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

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

Note: If you want to use the above code, please do read the documentation:

  • 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.


This is a runnable example:

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."),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
5
  • This doesn't work though. I tested it by switching between a 40x60 red container to a 60x40 green one; and there's no fade and the size transition is invalid Commented Aug 8, 2018 at 2:48
  • @RémiRousselet Please, read the documentation: The "old" and the "new" child must have the same width, but can have different heights. Also, if the type of the child widget is the same you must define different keys. Commented Aug 8, 2018 at 3:02
  • I think you can safely accept your own answer ;) One nitpick: It would be better to use ValueKey instead of GlobalKey, e.g. ValueKey('first') and ValueKey('second')
    – boformer
    Commented Aug 8, 2018 at 6:59
  • Consider extracting your doc in the answer. It's more readable this way. Commented Aug 8, 2018 at 9:21
  • @boformer and Remi: Done what both suggested. Commented Sep 17, 2018 at 20:12
2

There are many ways to achieve that. This is just an example:

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

Usage:

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

Refer to this documentation Flutter Animations

Updated

  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();
    }
  }
5
  • This is not what I want. As I said, I don't know the size of the children in advance. Also, I want to fade one widget to the other, and not transition the widget's opacity. Commented Aug 7, 2018 at 23:46
  • Do you have some video/gif about what you want? Did you try using Hero animation? Commented Aug 7, 2018 at 23:48
  • No I don't. I just want to transition size and fade when I change a child. Commented Aug 7, 2018 at 23:51
  • I updated my example using a custom : transitionBuilder , does it works for you? Commented Aug 8, 2018 at 0:21
  • Interesting effect, but what I want is to the size of the first widget to transition to the size of the second, while the first widget image transitions to the second widget image. What you did is the first widget is removed and the second one grows and fades in. Commented Aug 8, 2018 at 1:47

Not the answer you're looking for? Browse other questions tagged or ask your own question.