12

A problem with AnimatedCrossFade is that you must provide both children, even if only one of them is being displayed.

If one (or both) of those children is complex and heavy, this is not efficient.

I have tried to provide a Builder as a child, like this:

AnimatedCrossFade(
    firstChild: widget1,
    secondChild: Builder(builder: widget2builder),
    duration: const Duration(milliseconds: 500),
    crossFadeState: toggle ? CrossFadeState.showFirst : CrossFadeState.showSecond,
),

var widget2builder = (context) {
  print("Building widget 2");
  return Container(),
  );
};

However, this prints Building widget 2 right away, even if the first widget is the one being displayed and thus widget 2 is not needed at all.

This is a complete, runnable example:

import 'package:flutter/material.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 = true;
  }

  @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: UniqueKey(),
      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 widget2builder = (context) {
      print("Building widget 2.");
      return Container(
        key: UniqueKey(),
        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>[
            toggleButton,
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text("Some text above."),
                AnimatedCrossFade(
                  firstChild: widget1,
                  secondChild: Builder(builder: widget2builder),
                  duration: const Duration(milliseconds: 500),
                  crossFadeState: toggle ? CrossFadeState.showFirst : CrossFadeState.showSecond,
                ),
                const Text("Some text below."),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

If you run this, you will see that Building widget 2 is printed to the console, but widget 1 is the one being displayed.

My questions:

  • Is there any reason why AnimatedCrossFade is building the widget it doesn't use?

  • How can I prevent this problem and use AnimatedCrossFade efficiently?

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
  • In no situation should a widget be "complex and heavy". What do you mean by that ? – Rémi Rousselet Aug 08 '18 at 09:18
  • @RémiRousselet Widgets that have a lot of widgets inside of them. Widgets that do complex calculations. Widgets that access databases or the network to get info they need to build themselves. – Marcelo Glasberg Aug 08 '18 at 15:55

2 Answers2

7

I'd like to add to Rémi's answer that sometime after asking this question I've published a package to solve it, called AnimatedSizeAndFade:

https://pub.dev/packages/animated_size_and_fade

How does it compare to other similar widgets:

  • With AnimatedCrossFade you must keep both the firstChild and secondChild, which is not necessary with AnimatedSizeAndFade.

  • With AnimatedSwitcher you may simply change its child, but then it only animates the fade, not the size.

  • AnimatedContainer also doesn't work unless you know the size of the children in advance.

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
6

This is the expected behavior.

As stated by the dart team, you should expect the build method the be called at any times. build is designed to be cheap and without side effects.

The fact that a widget is built doesn't mean it's rendered on screen. Opacity with an opacity of 0 actually shortcuts the painting process (and have other optimizations when completely opaque).


If this causes a problem then you should instead use AnimatedSwitcher which plays an animation when its child is replaced.

Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432