Flutter How To - Expandable List Tile
Tags: flutter
Published: 2021-03-29 16:35:51

**TL;DR** here is the [GitHub project](https://github.com/fourman-alex/animated_expansion_list) with the implementation and a demo.

#### Why?
Showing information in a list is a very common pattern and often enough the list item will only show the most prominent details to save screen real-estate. Most apps will then show any extra details in a new screen. This decision has mostly been influenced by the difficulty to implement another approach, which is to show extra details still in context with the list. Flutter enables these animations in a way that is far easier to implement.

#### What?
This is what it will look like
![Final Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j09n93mw2qlx2d5hv4t2.gif)

#### How?
Well first we need to break it all down to the main components.
The way I see it we have our `ExpandListTile` widget which will need to receive the widget the is shown when it's collapsed and the widget that's shown when it's expanded.
So we will be switching between those two on a tap on `ExpandListTile`.
Now to add the animation we have the size animation where the container grows from collapsed to expanded and shrinks when reversed. The last animation to take care of would be cross-fading between the collapsed and the expanded widgets.

So to sum it up:
1. switch between collapsed and expanded on tap
2. animate the size between collapsed and expanded
3. animate the cross-fade between collapsed and expanded

While there are different way to implement this effect, I'll demonstrate the simpler way using implicit animations, using `AnimatedContainer` and `AnimatedSwitcher`.

##### switch between collapsed and expanded on tap
Making our `ExpandListTile` statefull where each tap sets a new state, expanded or collapsed.

##### animate the size between collapsed and expanded
`AnimatedContainer`'s `height` parameter which is also tied to our state will trigger the container's animation

##### animate the cross-fade between collapsed and expanded
`AnimatedSwitcher` will animate new children 

#### ExpandListTile
```dart
import 'package:flutter/material.dart';

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: 200),
      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> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    final child = _isExpanded ? widget.expandedChild : widget.collapsedChild;
    return GestureDetector(
      behavior: HitTestBehavior
          .translucent, //IMPORTANT: allows "empty" spaces to respond to events
      onTap: (() => setState(() => _isExpanded = !_isExpanded)),
      child: AnimatedContainer(
        curve: widget.curve,
        height: _isExpanded ? widget.expandedHeight : widget.collapsedHeight,
        duration: widget.duration,
        child: AnimatedSwitcher(
          duration: widget.duration,
          child: OverflowBox(
            key: ValueKey(_isExpanded),
            alignment: Alignment.topLeft,
            maxHeight:
                _isExpanded ? widget.expandedHeight : widget.collapsedHeight,
            child: child,
          ),
        ),
      ),
    );
  }
}

```

### FAQ
###### Why not just use `ExpansionPanelList`
While it is definitely one way to go about it, I don't like the fact that the title is always visible and that there is a dropdown icon.
###### Why `GestureDetector` has `behavior: HitTestBehavior.translucent`?
This ensures that when tapping on an empty space will still be intercepted. 
###### Why is there an `OverflowBox`?
while changing the size of the container we want the height of the expanded and collapsed children to be their final size, to avoid widgets changing layout. I find this have a more intuitive look.

**If you have a different way or any thoughts on this implementation, leave me a comment, I'd like to hear what you think.**