Diego Francisco
Diego Francisco

Reputation: 1910

Flutter - Animate change on height when child of container renders

I'm trying to recreate something like ExpansionTile but in a Card. When I click the card, its child renders and the card changes its height, so I want to animate that change.

I tried using AnimatedContainer and GlobalKey to know the final size of the card with its child rendered and then set the new height to AnimatedContainer but that didn't work.

Upvotes: 45

Views: 45310

Answers (4)

DK Kum
DK Kum

Reputation: 33

It works better for me

AnimatedCrossFade(
  duration: _controller.duration!,
  firstCurve: Curves.easeInOut,
  secondCurve: Curves.easeInOut,
  firstChild: Container(),
  secondChild: widget.content,
  crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
)

Upvotes: 2

Diego Francisco
Diego Francisco

Reputation: 1910

In the end I just had to use AnimatedSize. It replicates exactly the animation that I want.

AnimatedSize(
  vsync: this,
  duration: Duration(milliseconds: 150),
  curve: Curves.fastOutSlowIn,
  child: Container(
    child: Container(
      child: !_isExpanded
          ? null
          : FadeTransition(opacity: animationFade, child: widget.child),
    ),
  ),
);

Upvotes: 109

Lakhwinder Singh
Lakhwinder Singh

Reputation: 7209

You can use the AnimatedContainer for animations

class Animate extends StatefulWidget {
  @override
  _AnimateState createState() => _AnimateState();
}

class _AnimateState extends State<Animate> {
  var height = 200.0;

  @override
  Widget build(BuildContext context) {
    var size = MediaQuery.of(context).size;
    return Scaffold(
      body: Center(
        child: AnimatedContainer(
          color: Colors.amber,
          duration: new Duration(milliseconds: 500),
          height: height,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            if (height == 200.0) {
              height = 400.0;
            } else {
              height = 200.0;
            }
          });
        },
        child: Icon(Icons.settings),
      ),
    );
  }
}

Upvotes: 4

GoPro
GoPro

Reputation: 728

I tweaked the ExpansionTile, this has proper animation. Hope this helps

class _FixedExpansionTileState extends State<FixedExpansionTile> with SingleTickerProviderStateMixin {
  AnimationController _controller;
  CurvedAnimation _easeOutAnimation;
  CurvedAnimation _easeInAnimation;
  ColorTween _borderColor;
  ColorTween _headerColor;
  ColorTween _iconColor;
  ColorTween _backgroundColor;
  Animation<double> _iconTurns;

  bool _isExpanded = false;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(duration: _kExpand, vsync: this);
    _easeOutAnimation = new CurvedAnimation(parent: _controller, curve: Curves.easeOut);
    _easeInAnimation = new CurvedAnimation(parent: _controller, curve: Curves.easeIn);
    _borderColor = new ColorTween();
    _headerColor = new ColorTween();
    _iconColor = new ColorTween();
    _iconTurns = new Tween<double>(begin: 0.0, end: 0.5).animate(_easeInAnimation);
    _backgroundColor = new ColorTween();

    _isExpanded = PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded;
    if (_isExpanded)
      _controller.value = 1.0;
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _handleTap() {
    setState(() {
      _isExpanded = !_isExpanded;
      if (_isExpanded)
        _controller.forward();
      else
        _controller.reverse().then<void>((value) {
          setState(() {
            // Rebuild without widget.children.
          });
        });
      PageStorage.of(context)?.writeState(context, _isExpanded);
    });
    if (widget.onExpansionChanged != null)
      widget.onExpansionChanged(_isExpanded);
  }

  Widget _buildChildren(BuildContext context, Widget child) {
    final Color borderSideColor =  Colors.transparent;
  //  final Color titleColor = _headerColor.evaluate(_easeInAnimation);

    return new Container(
      decoration: new BoxDecoration(
        color: _backgroundColor.evaluate(_easeOutAnimation) ?? Colors.transparent,
        border: new Border(
          top: new BorderSide(color: borderSideColor),
          bottom: new BorderSide(color: borderSideColor),
        )
      ),
      child: new Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          IconTheme.merge(
            data: new IconThemeData(color: _iconColor.evaluate(_easeInAnimation)),
            child: new ListTile(
              onTap: _handleTap,
              leading: widget.leading,
              title: new DefaultTextStyle(
                style: Theme.of(context).textTheme.subhead.copyWith(color: Colors.transparent),
                child: widget.title,
              ),
              trailing: widget.trailing ?? new RotationTransition(
                turns: _iconTurns,
                child: const Icon(Icons.expand_more),
              ),
            ),
          ),
          new ClipRect(
            child: new Align(
              heightFactor: _easeInAnimation.value,
              child: child,
            ),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    _borderColor.end = theme.dividerColor;
    _headerColor
      ..begin = theme.textTheme.subhead.color
      ..end = theme.accentColor;
    _iconColor
      ..begin = theme.unselectedWidgetColor
      ..end = theme.accentColor;
    _backgroundColor.end = widget.backgroundColor;

    final bool closed = !_isExpanded && _controller.isDismissed;
    return new AnimatedBuilder(
      animation: _controller.view,
      builder: _buildChildren,
      child: closed ? null : new Column(children: widget.children),
    );

  }
}

Upvotes: 1

Related Questions