DEV Community

Cover image for Flutter ListView Expand And Flip Animation
Dev Journey
Dev Journey

Posted on Edited on Originally published at fourmanalex.com

Flutter ListView Expand And Flip Animation

After doing a fade-in and expand animation I wanted to have something that is really unique to Flutter's tool-set.
The animation was mainly inspired by this great talk Marcin Szałek | Flutter Europe

Flip and Expand animation

The flip animation

The code is available in the same GitHub project under expand_with_flip branch

Here is the main class for convenience

ExpandListTile
import 'package:flutter/material.dart';
import 'dart:math' as math;

class ExpandListTile extends StatefulWidget {
  const ExpandListTile(
      {Key key,
      this.expandedChild,
      this.collapsedChild,
      this.expandedHeight = 300.0,
      this.collapsedHeight = 70.0,
      this.duration = const Duration(milliseconds: 500),
      this.curve = Curves.ease})
      : super(key: key);

  final Widget expandedChild;
  final Widget collapsedChild;
  final double expandedHeight;
  final double collapsedHeight;
  final Duration duration;
  final Curve curve;

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

class _ExpandListTileState extends State<ExpandListTile>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation _flipAnimation;
  Animation _expandAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
    _flipAnimation = CurvedAnimation(
        parent: _controller, curve: Interval(0.5, 1.0, curve: Curves.ease));
    _expandAnimation = CurvedAnimation(
        parent: _controller, curve: Interval(0.0, 0.5, curve: Curves.ease));
  }

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

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      behavior: HitTestBehavior
          .translucent, //IMPORTANT: allows "empty" spaces to respond to events
      onTap: (() => setState(() {
            _toggle();
            // _isExpanded = !_isExpanded;
          })),
      child: AnimatedBuilder(
        animation: _flipAnimation,
        builder: (context, child) {
          var currentHeight = widget.collapsedHeight +
              (widget.expandedHeight - widget.collapsedHeight) *
                  _expandAnimation.value;
          return SizedBox(
            // height: widget.expandedHeight,
            height: currentHeight,
            child: Stack(
              children: [
                Transform.translate(
                  offset: Offset(
                      0, -widget.collapsedHeight * (_flipAnimation.value)),
                  child: Transform(
                    alignment: Alignment.bottomCenter,
                    transform: Matrix4.identity()
                      ..setEntry(2, 3, 0.001)
                      ..rotateX(-math.pi / 2 * _flipAnimation.value),
                    child: widget.collapsedChild,
                  ),
                ),
                Transform.translate(
                  offset: Offset(
                      0, -widget.collapsedHeight * (_flipAnimation.value - 1)),
                  child: Transform(
                    alignment: Alignment.topCenter,
                    transform: Matrix4.identity()
                      ..setEntry(2, 3, 0.001)
                      ..rotateX(math.pi * (1 - _flipAnimation.value) / 2),
                    child: OverflowBox(
                      maxHeight: widget.expandedHeight,
                      child: widget.expandedChild,
                    ),
                  ),
                )
              ],
            ),
          );
        },
      ),
    );
  }

  void _toggle() {
    if (_controller.status == AnimationStatus.completed ||
        _controller.status == AnimationStatus.forward) {
      _controller.reverse();
    } else {
      _controller.forward();
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

I'm keeping this post short but I can add more details on the implementation if this brings up some interest in people.

Top comments (0)