Flutter: Minimum height on horizontal list view

I'm trying to create a horizontal scrolling list of items in Flutter, and I want that list to only take up the necessary height based on its children. By design “ListView tries to expand to fit the space available in its cross-direction” (from the Flutter docs), which I also notice in that it takes up the whole height of the viewport, but is there a way to make it not do this? Ideally something similar to this (which obviously doesn't work):

new ListView(
  scrollDirection: Axis.horizontal,
  crossAxisSize: CrossAxisSize.min,
  children: <Widget>[
    new ListItem(),
    new ListItem(),
    // ...
  ],
);

I realize that one way to do this is by wrapping the ListView in a Container with a fixed height. However, I don't necessarily know the height of the items:

new Container(
  height: 97.0,
  child: new ListView(
    scrollDirection: Axis.horizontal,
    children: <Widget>[
      new ListItem(),
      new ListItem(),
      // ...
    ],
  ),
);

I was able to hack together a “solution” by nesting a Row in a SingleChildScrollView in a Column with a mainAxisSize: MainAxisSize.min. However, this doesn't feel like a solution, to me:

new Column(
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    new SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: new Row(
        children: <Widget>[
          new ListItem(),
          new ListItem(),
          // ...
        ],
      ),
    ),
  ],
);

Solution 1:

Just set shrink property of ListView to true and it will fit the space rather than expanding.

Example:

ListView(
        shrinkWrap: true, //just set this property
        padding: const EdgeInsets.all(8.0),
        children: listItems.toList(),
      ),

Solution 2:

Use ConstrainedBox to set minHeight and maxHeight

ConstrainedBox(
  constraints: new BoxConstraints(
    minHeight: 35.0,
    maxHeight: 160.0,
  ),

  child: new ListView(
    shrinkWrap: true,
    children: <Widget>[
      new ListItem(),
      new ListItem(),
    ],

  ),
)

Solution 3:

As far as I understand, you can't have a horizontal ListView inside a vertical ListView and have its height dynamically set. If your requirements allow (no infinite scrolling, small amount of elements, etc), you could use a SingleChildScrollView instead.

SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: [...],
  ),
);

Solution 4:

Use a SingleChildScrollView with scrollDirection: Axis.horizontal and a Row inside.

Big advantages:

  1. It doesn't matter how many of widgets you need.

  2. You don't need to know the heights of the widgets.

    Widget _horizontalWrappedRow(List data) {
    
     var list = <Widget>[SizedBox(width: 16)]; // 16 is start padding
    
     //create a new row widget for each data element
     data.forEach((element) {
        list.add(MyRowItemWidget(element));
     });
    
     // add the list of widgets to the Row as children
     return SingleChildScrollView(
       scrollDirection: Axis.horizontal,
       child: Row(
         crossAxisAlignment: CrossAxisAlignment.start,
         children: list,
       ),
     );
    

    }

Solution 5:

This is very similar to a question asked here: Flutter ListView.builder() widget's cross Axis is taking up the entire Screen height

I believe ListViews require every item to have the same Cross Axis size. That means in this case, we are unable to set a unique height for every object, the cross axis size is fixed.

If you want to have the height of a scrollable uniquely controlled by the child itself, then you can use a SingleChildScrollView paired with a Row (note: make sure to set the scrollDirection: Axis.horizontal) output

import 'package:flutter/material.dart';

final Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SafeArea(
          child: Container(
              // height: 100, // no need to specify here
              color: Colors.white,
              child: SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child: Row(
                  children: <Widget>[
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                  ],
                ),
              )),
        ),
      ),
    );
  }
}