Flutter: Minimum height on horizontal list view
ListviewDartFlutterListview Problem Overview
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(),
// ...
],
),
),
],
);
Listview Solutions
Solution 1 - Listview
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 - Listview
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 - Listview
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 - Listview
Use a SingleChildScrollView
with scrollDirection: Axis.horizontal
and a Row
inside.
Big advantages:
-
It doesn't matter how many of widgets you need.
-
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 - Listview
This is very similar to a question asked here: https://stackoverflow.com/questions/65996711/flutter-listview-builder-widgets-cross-axis-is-taking-up-the-entire-screen-he/65998731#65998731
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)
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')),
),
],
),
)),
),
),
);
}
}
Solution 6 - Listview
I'm using a horizontal listview builder of container so to stop the Container for taking the full height i just wrapped it with Center and it worked perfectly.
itemBuilder: (context, i){
return Center(child: word_on_line(titles[i]));
}
Solution 7 - Listview
Use Expanded
Expanded(
child:ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.all(10),
separatorBuilder: (BuildContext context, int index) {
return Align(
alignment: Alignment.centerRight,
child: Container(
height: 0.5,
width: MediaQuery.of(context).size.width / 1.3,
child: Divider(),
),
);
},
itemCount: dataMasterClass.length,
itemBuilder: (BuildContext context, int index) {
Datum datum = dataMasterClass[index];
return sendItem(datum);
},)
) ,
Solution 8 - Listview
Wrapping the widget by Column
with mainAxisSize: MainAxisSize.min
worked for me. You can try to change the height
here.
var data = [ {'name': 'Shopping', 'icon': Icons.local_shipping}, {'name': 'Service', 'icon': Icons.room_service}, {'name': 'Hotel', 'icon': Icons.hotel}, {'name': 'More', 'icon': Icons.more}];
new Container(
constraints: new BoxConstraints(
minHeight: 40.0,
maxHeight: 60.0,
),
color: Colors.transparent,
child: new ListView(
scrollDirection: Axis.horizontal,
children: data
.map<Widget>((e) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Container(
width: 40,
height: 50, // try to change this.
color: Colors.transparent,
margin: EdgeInsets.only(right: 20, left: 4),
child: ClipOval(
child: Container(
padding: EdgeInsets.all(4),
color: Colors.white,
child: Icon(
e["icon"],
color: Colors.grey,
size: 30,
),
),
),
),
],
))
.toList(),
));
Solution 9 - Listview
By applying all the above solution there is no suitable answer found yet, which help us to set horizontal Listview If we don't know the height. we must have to set height in all the cases. so I applied the below solution which works for me without specifying any height for the list items. which @sindrenm has already mentioned in his question. so I would like to go with it till a feasible solution will found. I applied shrinkWrap: true but it will shrink the ListView along the main-axis. (only for vertical scrollView) and Not along the cross-axis as asked in the question. so in the nearest future, anyone can go with this solution in the production as well, it works great for me for my all horizontal lists.
//below declared somewhere in a class above any method and assume list has filled from some API.
var mylist = List<myModel>();
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
for (int index = 0; index < mylist.length; index++)
listItem(mylist[index]);
],
),
),
),
listItem(myModelObj){
return Column(
children[
widget1(),
widget2().... etc..
]);
}
Solution 10 - Listview
If you do not want the listview to force your widget to fit in the cross axis make suure that they are placed in a Column() or Row() widget instead of placing it directly in a listview()
new ListView(
scrollDirection: Axis.horizontal,
crossAxisSize: CrossAxisSize.min,
children: <Widget>[
Column(
children:[
new ListItem(),
new ListItem(),
],
)
],
);
Solution 11 - Listview
This should not be done with a ListView
, and for a good reason:
A ListView
could potentially have a lot of items (or even unlimited), but figuring out "the biggest one" requires going through every item, which is against the whole point of using a ListView
(dynamically load each item using the builder
method).
On the other hand, a Row
widget always lay out all children at once, so it's easy to find the biggest one. So if you don't have too many items (perhaps less than 100 items or so), you can use Row
instead. If you need scrolling, wrap Row
with a SingleChildScrollView
.
Code used in the example:
Column(
children: [
Container(
color: Colors.green.shade100,
child: Scrollbar(
child: SingleChildScrollView(
primary: true,
scrollDirection: Axis.horizontal,
child: Row(
children: [
FlutterLogo(),
FlutterLogo(size: 100),
for (int i = 0; i < 50; i++) FlutterLogo(),
],
),
),
),
),
const Text('The row has ended.'),
],
)