Search code examples
androidflutterdartlayoutanimatedcrossfade

Flutter AnimatedCrossFade messes up widget formatting


I'm trying to create a smooth animation using the AnimatedCrossFade widget but I noticed 2 problems:

Button dimension changes and expands during animation.

The desired outcome is that both buttons match the parent's width and that the color and text changes transition smoothly, but here's what happens.

  • Without AnimatedCrossFade, Button 1 looks like this:

Button 1 without AnimatedCrossFade

  • If I wrap it inside an AnimatedCrossFade widget, Button 1 looks like this:

Button 1 with AnimatedCrossFade

  • While the transition is happening, It looks like this:

Button Transitions

TextField with InputDecoration stroke becomes thinner

I have multiple TextField widgets that I want to use in the page but some need to be animated in. The problem is that when I put a TextField inside an AnimatedCrossFade widget, the bottom line becomes thinner making the layout look horrible. Here's a comparison of how a TextField looks inside an AnimatedCrossFade (top) and outside of it (bottom).

TextField differences

  • This is how the layout looks after animation.

Layout before

  • But it should look like this.

Layout after

This code sample should be enough to recreate what I'm trying to explain.

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView(
        padding: EdgeInsets.symmetric(horizontal: 60, vertical: 60),
        children: [
          ElevatedButton(
            child: Text(_isExpanded ? "Collapse" : "Expand"),
            onPressed: () {
              setState(() {
                _isExpanded = !_isExpanded;
              });
            },
          ),
          AnimatedCrossFade(
            crossFadeState: _isExpanded
                ? CrossFadeState.showFirst
                : CrossFadeState.showSecond,
            duration: const Duration(seconds: 1),
            firstChild: TextField(
              decoration: InputDecoration(
                hintText: "Text",
              ),
            ),
            secondChild: SizedBox.shrink(),
          ),
          TextField(
            decoration: InputDecoration(
              hintText: "Text",
            ),
          ),
          AnimatedCrossFade(
            crossFadeState: !_isExpanded
                ? CrossFadeState.showFirst
                : CrossFadeState.showSecond,
            duration: Duration(seconds: 1),
            firstChild: ElevatedButton(
              child: Text("Button 1"),
              onPressed: () {},
            ),
            secondChild: ElevatedButton(
              child: Text("Button 2"),
              style: ButtonStyle(
                backgroundColor: MaterialStateProperty.all(Colors.red),
              ),
              onPressed: () {},
            ),
          ),
        ],
      ),
    );
  }
}

Solution

  • Hope this is what you want?

    result

    I think to handle this case, you need to use layoutBuilder of AnimatedCrossFade if you click on layoutBuilder you can find details.

    Updated wrap with padding to solve TextFiledFormat, for more you can use decoration.

    enter image description here

    To use max width i used like this

      AnimatedCrossFade(
                crossFadeState: _isExpanded
                    ? CrossFadeState.showFirst
                    : CrossFadeState.showSecond,
                duration: const Duration(seconds: 1),
                firstChild: Padding(
                  padding: const EdgeInsets.symmetric(vertical: 4),
                  child: TextField(
                    key: ValueKey("text1"),
                    decoration: InputDecoration(
                      hintText: "Text1",
                    ),
                  ),
                ),
                secondChild: SizedBox.shrink(),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 4),
                child: TextField(
                  key: ValueKey("text2"),
                  decoration: InputDecoration(
                    hintText: "Text",
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 4.0),
                child: AnimatedCrossFade(
                  crossFadeState: !_isExpanded
                      ? CrossFadeState.showFirst
                      : CrossFadeState.showSecond,
                  duration: Duration(seconds: 1),
                  alignment: Alignment.center,
                  layoutBuilder:
                      (topChild, topChildKey, bottomChild, bottomChildKey) {
                    return topChild;
                  },
                  secondChild: ElevatedButton(
                    child: Text("Button 2"),
                    style: ButtonStyle(
                      backgroundColor: MaterialStateProperty.all(Colors.red),
                    ),
                    onPressed: () {},
                  ),
                  firstChild: ElevatedButton(
                    child: Text("Button 1"),
                    style: ButtonStyle(
                      backgroundColor: MaterialStateProperty.all(Colors.red),
                    ),
                    onPressed: () {},
                  ),
                ),
              ),
    

    To get default size of button wrapped with Center inside layoutBuilder

      layoutBuilder:
                    (topChild, topChildKey, bottomChild, bottomChildKey) {
                  return topChild;
                },