How do you animate to expand a container from 0 height to the height of its contents in Flutter?

1,477

Solution 1

Maybe you could also solve this with a SizeTransition?

enter image description here

class VariableSizeContainerExample extends StatefulWidget {
  VariableSizeContainerExample();

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

class _VariableSizeContainerExampleState extends State<VariableSizeContainerExample> with TickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    );
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.fastLinearToSlowEaseIn,
    );
  }

  _toggleContainer() {
    print(_animation.status);
    if (_animation.status != AnimationStatus.completed) {
      _controller.forward();
    } else {
      _controller.animateBack(0, duration: Duration(seconds: 1));
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: Column(
            children: [
              TextButton(
                onPressed: () => _toggleContainer(),
                child: Text("Toggle container visibility"),
              ),
              SizeTransition(
                sizeFactor: _animation,
                axis: Axis.vertical,
                child: Container(
                  child: Text(
                    "This can have variable size",
                    style: TextStyle(fontSize: 40),
                  ),
                ),
              ),
              Text("This is below the above container"),
            ],
          ),
        ),
      ),
    );
  }
}

Solution 2

Moving @pskink's comments to an answer for posterity:

The main concept is that the Align widget has a property called heightFactor, which takes a double between 0 and 1 to scale its child's height (there's also a similar widthFactor property for width). By animating this property, we can collapse/expand the child. For example:

ClipRect(
      child: Align(
        alignment: alignment,
        child: Align(
          alignment: innerAlignment,
          widthFactor: constantValue,
          heightFactor: animatedValue.value,
          child: builder(context, animation),
        ),
      )
)

where animatedValue is of type Animation<double>, and ClipReact is used to clip/truncate the child widget. Note that ClipReact needs to be wrapped outside the Align widget; it doesn't work consistently when wrapping Align's child widget.

Edit: it's also necessary for the recipient of the animation to be an AnimatedWidget for things to go smoothly. See selected answer for an approach that handles this for you.

Share:
1,477
mwarrior
Author by

mwarrior

Updated on December 28, 2022

Comments

  • mwarrior
    mwarrior over 1 year

    I have a container that starts at zero height and needs to be expanded after a user interaction.

    • I tried using AnimatedContainer / AnimatedSize and changing the child widget's height from 0 to null, but in both cases, Flutter complains that it cant' interpolate from 0 to null.
    • I've also tried using BoxConstraints (with expanded using maxHeight = double.infinity) instead of explicit heights, in which case Flutter complains it can't interpolate from a finite value to an indefinite one.
    • I've also tried setting mainAxisSize to min/max, in which case Flutter complains that vsync is null.

    How do I animate expanding a widget such that it dynamically grows big enough to wrap its contents? And if this can't be done dynamically, what's a safe way to size contents such that they make sense across screen sizes? In web dev, I know things like em are sort of relative sizing, but in the context of Flutter, I don't see how to control the size of things reliably.


    Update: As suggested by @pskink, wrapping the child in an Align widget and animating Align's heightFactor param accomplishes collapsing. However, I'm still having trouble getting collapse to work when the collapsing child itself has children. For example, Column widgets don't clip at all with ClipRect (see https://github.com/flutter/flutter/issues/29357), and even if I use Wrap instead of Column, that doesn't work if the Wrap's children are Rows. Not sure how to get clipping to work consistently.

  • mwarrior
    mwarrior over 3 years
    This worked great! @pskink's answer works too, but required a bit more wiring. This one does exactly what I want in a straightforward way. I want to also mention that if anyone wants it to collapse from the bottom (as opposed to toward the middle), you can add axisAlignment: -1 to the SizeTransition params