Flutter dynamic height of ListView

Viewed 32923

I am developing a Flutter application and I want to dynamically adjust the height of the ListView.

I want the list to have a maximum height of 200. If the height is more than that, user will have to scroll to reach the bottom. If height is below 200, it will take only as much space as needed.

Preview of application: Screenshot. As you can see Restaurants nearby is pushed to the very bottom of the page. I want the ListView to only take height of 200 or less so that the content below isn't pushed to the bottom.

Here is my current code:

@override
Widget build(BuildContext context) {
  return Container(
    padding: EdgeInsets.all(10.0),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Text(
          'Restaurants nearby',
          style: TextStyle(
            fontSize: 20.0,
            fontWeight: FontWeight.bold,
          ),
        ),
        Divider(),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text('Enter restaurant manually'),
              onPressed: () {
                print('Button pressed');
              },
            ),
          ],
        ),
        Flexible(
          child: ListView.builder(
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                leading: CircleAvatar(
                  backgroundColor: Colors.cyan,
                ),
                title: Text('Test restaurant'),
                subtitle: Text('80m'),
              );
            },
            itemCount: 15,
          ),
        ),
        Text(
          'Restaurants nearby',
          style: TextStyle(
            fontSize: 20.0,
            fontWeight: FontWeight.bold,
          ),
        ),
      ],
    ),
  );
}
7 Answers

You are using Flexible widget, that's why your ListView expands. You have to change Flexible to ConstrainedBox and add shrinkWrap: true to your ListView.

ConstrainedBox(
  constraints: BoxConstraints(maxHeight: 200, minHeight: 56.0),
  child: ListView.builder(
    shrinkWrap: true,
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          leading: CircleAvatar(
            backgroundColor: Colors.cyan,
          ),
          title: Text('Test restaurant'),
          subtitle: Text('80m'),
        );
      },
    itemCount: 15,
  ),
),

More info here: https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html

You can use LimitedBox

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Text(...),
    Divider(),
    Row(...),
    LimitedBox(
      maxHeight: 200.0,
      child: ListView.builder(...),
    ),
    Text(...),
  ],
),

Recommended solution in case when the incoming constraints are unbounded

Consider wrapping the ListView into this

LimitedBox(
  maxHeight: 200,
  child: Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      Flexible(
        child: ListView.builder(
          shrinkWrap: true,
          itemBuilder: (BuildContext context, int index) {
            return ListTile(
              leading: CircleAvatar(
                backgroundColor: Colors.cyan,
              ),
              title: Text('Test restaurant'),
              subtitle: Text('80m'),
            );
          },
          itemCount: _itemsCount,
        ),
      ),
    ]
  )
),

Note that:

  • shrinkWrap of ListView is set to true
  • mainAxisSize of Column is set to MainAxisSize.min
  • maxHeight of LimitedBox is set to 200

A complete snippet:

import 'package:flutter/material.dart';

class DebugWidget extends StatefulWidget {
  @override
  _DebugWidgetState createState() => _DebugWidgetState();
}
class _DebugWidgetState extends State<DebugWidget> {
  int _itemsCount = 1;

  @override
  Widget build(BuildContext context) {
    Widget child = Container(
      padding: EdgeInsets.all(10.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text(
            'Restaurants nearby',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
            ),
          ),
          Divider(),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              RaisedButton(
                child: Text('Enter restaurant manually'),
                onPressed: () {
                  print('Button pressed');
                },
              ),
              RaisedButton(
                child: Text('+1'),
                onPressed: () {
                  setState(() {
                    _itemsCount += 1;
                  });
                },
              ),
              RaisedButton(
                child: Text('-1'),
                onPressed: () {
                  setState(() {
                    _itemsCount -= 1;
                  });
                },
              ),
            ],
          ),
          LimitedBox(
            maxHeight: 200,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Flexible(
                  child: ListView.builder(
                    shrinkWrap: true,
                    itemBuilder: (BuildContext context, int index) {
                      return ListTile(
                        leading: CircleAvatar(
                          backgroundColor: Colors.cyan,
                        ),
                        title: Text('Test restaurant'),
                        subtitle: Text('80m'),
                      );
                    },
                    itemCount: _itemsCount,
                  ),
                ),
              ]
            )
          ),
          Text(
            'Restaurants nearby',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );

    return Scaffold(
      body: child,
    );
  }
}

I found a solution to this problem. you should wrap your ListView with LimittedBox or ConstraintBox and give them maxHeight and set shrinkWrap property of ListView to true. the solution would be something like this.

LimitedBox(
          maxHeight: 200,
          child: ListView.builder(
            shrinkWrap: true,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                leading: CircleAvatar(
                  backgroundColor: Colors.cyan,
                ),
                title: Text('Test restaurant'),
                subtitle: Text('80m'),
              );
            },
            itemCount: 15,
          ),
        ),

I see that this question has never been answered only with giving a fixed height, so here is what works for me.

For some reason if you set the shrinkwrap to true it doesn't look like it is working but it does, the problem is in the padding settings of the ListView, set the padding to edgeinstets.zero. That fixes it for me.

  • Wrap inside a Flexible
  • ShrinkWrap true
  • Padding, zero
  • and if needed the column to MainAxisSize.min.

Hope it helps some people.

Example of my code:

Flexible(
                  child: Container(
                    decoration: BStyles.cardDecoration1,
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          const Text(
                            'PRODUCTION TASKS',
                          ),
                          const SizedBox(
                            height: textVerticalSpacing,
                          ),
                          Flexible(
                            child: ListView.builder(
                              itemCount: recentTasks.length,
                              padding: EdgeInsets.zero,
                              physics: const NeverScrollableScrollPhysics(),
                              shrinkWrap: true,
                              itemBuilder: (BuildContext context, int index) {
                                return TaskCard(
                                  task: recentTasks[index],
                                  widthInfo: MediaQuery.of(context).size.width * 0.6,
                                );
                              },
                            ),
                          ),
                          const SizedBox(
                            height: itemSpacing,
                          ),
                          Align(
                            alignment: Alignment.centerRight,
                            child: InkWell(
                              onTap: () { },
                              child: const Text(
                                'View more',
                              ),
                            ),
                          ),
                          const SizedBox(
                            height: textVerticalSpacing,
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
ConstrainedBox(
  constraints: BoxConstraints(maxHeight: 200.0),
  child: [your child here],
)

This make your child's height not bigger than 200.0

You can always size the ListView container as % of the viewport (assuming of course that the other widgets also are sized in the same manner):

return Container(
      height: MediaQuery.of(context).size.height * 0.75,
      child: ListView.builder(
        itemBuilder: (ctx, index) {
          return Card(...
Related