I'm using a Flutter modal bottom sheet to display some options for the user to select. I have a Column with a list of ListTiles as the content of the bottom sheet.

My problem is that if I have more than 6 ListTiles, some are cut off and not displayed.

Is there a way to make the bottom Sheet scrollable?


Solution 1: Marcel

Just change your Column into a ListView, like so:

return ListView(
  children: <Widget>[
    ...
  ]
);

What if I don't want the content of the sheet to be scrolled, but the sheet itself?

If you want the user to be able to swipe up the bottom sheet to fill the screen, I'm afraid this isn't possible in the current implementation of the modular bottom sheet.


Solution 2: Darish

If you want a model bottom sheet that can be scrolled.

You can use the isScrollControlled attribute of showModalBottomSheet to achieve the effect.


If you want a persistent bottom sheet that can be scrolled.

You can use the DraggableScrollableSheet

Example:

    class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('DraggableScrollableSheet'),
      ),
      body: SizedBox.expand(
        child: DraggableScrollableSheet(
          builder: (BuildContext context, ScrollController scrollController) {
            return Container(
              color: Colors.blue[100],
              child: ListView.builder(
                controller: scrollController,
                itemCount: 25,
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(title: Text('Item $index'));
                },
              ),
            );
          },
        ),
      ),
    );
  }
}

Here is the official video from the Flutter team.

A live demo on DartPad can be found here.


Solution 3: Elias Meireles

I've found a solution implementing the code below

void _showBottomSheet(BuildContext context) {
showModalBottomSheet(
  context: context,
  isScrollControlled: true,
  backgroundColor: Colors.transparent,
  builder: (context) {
    return GestureDetector(
      onTap: () => Navigator.of(context).pop(),
      child: Container(
        color: Color.fromRGBO(0, 0, 0, 0.001),
        child: GestureDetector(
          onTap: () {},
          child: DraggableScrollableSheet(
            initialChildSize: 0.4,
            minChildSize: 0.2,
            maxChildSize: 0.75,
            builder: (_, controller) {
              return Container(
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.only(
                    topLeft: const Radius.circular(25.0),
                    topRight: const Radius.circular(25.0),
                  ),
                ),
                child: Column(
                  children: [
                    Icon(
                      Icons.remove,
                      color: Colors.grey[600],
                    ),
                    Expanded(
                      child: ListView.builder(
                        controller: controller,
                        itemCount: 100,
                        itemBuilder: (_, index) {
                          return Card(
                            child: Padding(
                              padding: EdgeInsets.all(8),
                              child: Text("Element at index($index)"),
                            ),
                          );
                        },
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );
  },
 );
}


Solution 4: Manti

Almost the same solution like this one but without the unnecessary layers of gesture detectors etc.

The important part is the expand: false, in DraggableScrollableSheet, because it defaults to true. This causes the bottom sheet to expand to full height in default config. With this set to false there is no need to wrap the bottom sheet with two gesture detectors to detect the outside tap.

Also in this case only one shape is required.

showModalBottomSheet(
  context: context,
  isScrollControlled: true,
  isDismissible: true,
  shape: const RoundedRectangleBorder(
      borderRadius:
          BorderRadius.vertical(top: Radius.circular(16))),
  builder: (context) => DraggableScrollableSheet(
    initialChildSize: 0.4,
    minChildSize: 0.2,
    maxChildSize: 0.75,
    expand: false,
    builder: (_, controller) => Column(
      children: [
        Icon(
          Icons.remove,
          color: Colors.grey[600],
        ),
        Expanded(
          child: ListView.builder(
            controller: controller,
            itemCount: 100,
            itemBuilder: (_, index) {
              return Card(
                child: Padding(
                  padding: EdgeInsets.all(8),
                  child: Text("Element at index($index)"),
                ),
              );
            },
          ),
        ),
      ],
    ),
  ),
);