How to create Expandable ListView in Flutter

61,168

Solution 1

Try this:

import 'package:flutter/material.dart';

void main() => runApp(new MaterialApp(home: new MyApp(), debugShowCheckedModeBanner: false,),);

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new ListView.builder(
        itemCount: vehicles.length,
        itemBuilder: (context, i) {
          return new ExpansionTile(
            title: new Text(vehicles[i].title, style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic),),
            children: <Widget>[
              new Column(
                children: _buildExpandableContent(vehicles[i]),
              ),
            ],
          );
        },
      ),
    );
  }

  _buildExpandableContent(Vehicle vehicle) {
    List<Widget> columnContent = [];

    for (String content in vehicle.contents)
      columnContent.add(
        new ListTile(
          title: new Text(content, style: new TextStyle(fontSize: 18.0),),
          leading: new Icon(vehicle.icon),
        ),
      );

    return columnContent;
  }
}

class Vehicle {
  final String title;
  List<String> contents = [];
  final IconData icon;

  Vehicle(this.title, this.contents, this.icon);
}

List<Vehicle> vehicles = [
  new Vehicle(
    'Bike',
    ['Vehicle no. 1', 'Vehicle no. 2', 'Vehicle no. 7', 'Vehicle no. 10'],
    Icons.motorcycle,
  ),
  new Vehicle(
    'Cars',
    ['Vehicle no. 3', 'Vehicle no. 4', 'Vehicle no. 6'],
    Icons.directions_car,
  ),
];

Solution 2

Try this:

First Make an ExpandableContainer using AnimatedContainer.

Then Make an ExpandableListView which will create a Column . The first child of Column will be a button to expand and Second will be ExpandableContainer . ExpandableContainer will have a ListView as its child.

The last step will be to make a ListView of ExpandableListView.

The Result :

ExpandableLIstView

The Code :

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(home: new Home()));
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      backgroundColor: Colors.grey,
      appBar: new AppBar(
        title: new Text("Expandable List"),
        backgroundColor: Colors.redAccent,
      ),
      body: new ListView.builder(
        itemBuilder: (BuildContext context, int index) {
          return new ExpandableListView(title: "Title $index");
        },
        itemCount: 5,
      ),
    );
  }
}

class ExpandableListView extends StatefulWidget {
  final String title;

  const ExpandableListView({Key key, this.title}) : super(key: key);

  @override
  _ExpandableListViewState createState() => new _ExpandableListViewState();
}

class _ExpandableListViewState extends State<ExpandableListView> {
  bool expandFlag = false;

  @override
  Widget build(BuildContext context) {
    return new Container(
      margin: new EdgeInsets.symmetric(vertical: 1.0),
      child: new Column(
        children: <Widget>[
          new Container(
            color: Colors.blue,
            padding: new EdgeInsets.symmetric(horizontal: 5.0),
            child: new Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                new IconButton(
                    icon: new Container(
                      height: 50.0,
                      width: 50.0,
                      decoration: new BoxDecoration(
                        color: Colors.orange,
                        shape: BoxShape.circle,
                      ),
                      child: new Center(
                        child: new Icon(
                          expandFlag ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
                          color: Colors.white,
                          size: 30.0,
                        ),
                      ),
                    ),
                    onPressed: () {
                      setState(() {
                        expandFlag = !expandFlag;
                      });
                    }),
                new Text(
                  widget.title,
                  style: new TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
                )
              ],
            ),
          ),
          new ExpandableContainer(
              expanded: expandFlag,
              child: new ListView.builder(
                itemBuilder: (BuildContext context, int index) {
                  return new Container(
                    decoration:
                        new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.grey), color: Colors.black),
                    child: new ListTile(
                      title: new Text(
                        "Cool $index",
                        style: new TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
                      ),
                      leading: new Icon(
                        Icons.local_pizza,
                        color: Colors.white,
                      ),
                    ),
                  );
                },
                itemCount: 15,
              ))
        ],
      ),
    );
  }
}

class ExpandableContainer extends StatelessWidget {
  final bool expanded;
  final double collapsedHeight;
  final double expandedHeight;
  final Widget child;

  ExpandableContainer({
    @required this.child,
    this.collapsedHeight = 0.0,
    this.expandedHeight = 300.0,
    this.expanded = true,
  });

  @override
  Widget build(BuildContext context) {
    double screenWidth = MediaQuery.of(context).size.width;
    return new AnimatedContainer(
      duration: new Duration(milliseconds: 500),
      curve: Curves.easeInOut,
      width: screenWidth,
      height: expanded ? expandedHeight : collapsedHeight,
      child: new Container(
        child: child,
        decoration: new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.blue)),
      ),
    );
  }
}

Solution 3

Screenshot:

enter image description here


Code:

class MyPage extends StatelessWidget {
  List<Widget> _getChildren(int count, String name) => List<Widget>.generate(
        count,
        (i) => ListTile(title: Text('$name$i')),
      );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          ExpansionTile(
            title: Text('List-A'),
            children: _getChildren(4, 'A-'),
          ),
          ExpansionTile(
            title: Text('List-B'),
            children: _getChildren(3, 'B-'),
          ),
        ],
      ),
    );
  }
}
Share:
61,168
Melvin Abraham
Author by

Melvin Abraham

Updated on May 18, 2021

Comments

  • Melvin Abraham
    Melvin Abraham almost 3 years

    How to make an Expandable ListView using Flutter like the screenshot below?

    I want to make a scrollable list view of ExpansionTileswhich when expanded shows a non-scrollable list view.

    I tried to implement list view of ExpansionTiles inside which I nested another list view using listView.builder(...). But when I expanded the ExpansionTile the list view didn't show up...

    Expandable ListView

    (The screenshot is for illustrative purpose)

    Is there a way to get similar output in Flutter?

    EDIT: My Source Code:

    import 'package:flutter/material.dart';
    
    void main() => runApp(
      new MaterialApp(
        home: new MyApp(),
      )
    );
    
    var data = {
      "01/01/2018": [
        ["CocaCola", "\$ 5"],
        ["Dominos Pizza", "\$ 50"],
      ],
    
      "04/01/2018": [
        ["Appy Fizz", "\$ 10"],
        ["Galaxy S9+", "\$ 700"],
        ["Apple iPhone X", "\$ 999"],
      ],
    };
    
    List<String> dataKeys = data.keys.toList();
    
    String getFullDate(String date) {
      List<String> dateSplit = date.split('/');
      List<String> months = ["Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sep", "Oct", "Nov", "Dec"];
    
      return "${dateSplit[0]} ${months[int.parse(dateSplit[1]) - 1]} ${dateSplit[2]}";
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => new _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      List<Widget> _buildList(int keyIndex) {
        List<Widget> list = [];
    
        for (int i = 0; i < data[dataKeys[keyIndex]].length; i++) {
          list.add(
            new Row(
              children: <Widget>[
                new CircleAvatar(
                  child: new Icon(Icons.verified_user),
                  radius: 20.0,
                ),
                new Text(data[dataKeys[keyIndex]][i][0])
              ],
            )
          );
        }
    
        return list;
      }
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text("Expense Monitor"),
          ),
          body: new Container (
            child: new ListView.builder(
              itemCount: dataKeys.length,
              itemBuilder: (BuildContext context, int keyIndex) {
                return new Card(
                  child: new ExpansionTile(
                    title: new Text(getFullDate(dataKeys[keyIndex])),
                    children: <Widget>[
                      new Column(
                        children: _buildList(keyIndex)
                      )
                    ]
                  ),
                );
              }
            )
          )
        );
      }
    }
    

    Error as shown in Console:

    I/flutter (12945): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
    I/flutter (12945): The following assertion was thrown during performResize():
    I/flutter (12945): Vertical viewport was given unbounded height.
    I/flutter (12945): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
    I/flutter (12945): viewport was given an unlimited amount of vertical space in which to expand. This situation
    I/flutter (12945): typically happens when a scrollable widget is nested inside another scrollable widget.
    I/flutter (12945): If this widget is always nested in a scrollable widget there is no need to use a viewport because
    I/flutter (12945): there will always be enough vertical space for the children. In this case, consider using a Column
    I/flutter (12945): instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
    I/flutter (12945): the height of the viewport to the sum of the heights of its children.
    I/flutter (12945): When the exception was thrown, this was the stack:
    I/flutter (12945): #0      RenderViewport.performResize.<anonymous closure> (package:flutter/src/rendering/viewport.dart:944:15)
    I/flutter (12945): #1      RenderViewport.performResize (package:flutter/src/rendering/viewport.dart:997:6)
    I/flutter (12945): #2      RenderObject.layout (package:flutter/src/rendering/object.dart:1555:9)
    I/flutter (12945): #3      _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:109:13)
    ......
    I/flutter (12945): ════════════════════════════════════════════════════════════════════════════════════════════════════
    I/flutter (12945): Another exception was thrown: RenderBox was not laid out: RenderViewport#df29c NEEDS-LAYOUT NEEDS-PAINT