Shrink list view when less items are present - flutter

I'm trying to make a widget that displays two list view (one under the groups header, the other under the alerts header) with a minimum and maximum height.
I've managed to get maximum heights working via a ConstrainedBox wrapping everything however I've been unable to get a mimnimum height working. Idearly I would like the lists to like everything to shink if there are fewer items in both lists.
For example, in the screenshot below there are only two items in one list and none in the other so I would like the overall widget to shrink down to roughly half its current size
From what I can see in the Widget Inspector, the list itself is not taking up any more space than it needs however the Column appears to be taking up all the space the parent constraint is allowing for. I've tried messing with mainAxisSize: MainAxisSize.min, howeve this doesn't appear to have had any effect.
How can I prevent this column from taking all the extra space?
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 300, minHeight: 50),
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Flexible(
flex: 1,
child: Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Padding(padding: EdgeInsets.all(10), child: Text("Groups")),
const Divider(),
ListView.builder(
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) => ListTile(
...
))
],
),
),
),
Flexible(
flex: 1,
child: Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Padding(padding: EdgeInsets.all(10), child: Text("Alerts")),
const Divider(),
ListView.builder(
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) => ListTile(
...
))
],
),
),
),
],
),
);
Edit after suggestions:

Try this
Remove crossAxisAlignment: CrossAxisAlignment.stretch from the row
Add mainAxisSize: MainAxisSize.min to the columns
Edit (in response to the comment):
To make the lists same size. Not sure if there is a straightforward way.
One hacky way I can think of is by setting the itemCount of both lists equal to the length of the list with most items. And in itemBuilder, return an empty ListTile when the index is out of available items to show. Example -
import 'dart:math';
const groupsCount = 2;
const alertsCount = 4;
final biggestCount = max(groupsCount, alertsCount);
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(padding: EdgeInsets.all(10), child: Text("Groups")),
const Divider(),
ListView.builder(
shrinkWrap: true,
itemCount: biggestCount,
itemBuilder: (_, index) {
if (index < groupsCount) {
return ListTile(title: Text('Group $index'));
} else {
return const ListTile();
}
},
)
],
),
),
),
Expanded(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(padding: EdgeInsets.all(10), child: Text("Alerts")),
const Divider(),
ListView.builder(
shrinkWrap: true,
itemCount: biggestCount,
itemBuilder: (_, index) {
if (index < alertsCount) {
return ListTile(title: Text('Alert $index'));
} else {
return const ListTile();
}
},
)
],
),
),
),
],
);

Related

Flutter - Column MainAxisAlignment spaceBetween doesn't work inside Row

Good day.
I am trying to build a UI where the widget tree is like Row -> children(Column, List). The problem is I want my column to take the same height as the List. It is not happening. I am including screenshots and my code here. Any kind of help is appreciable
You can see that the column on the left is not taking all the space and space between the time and expanding more icons is not working either.
I am including my code here.
class CollapsibleAgendaList extends StatelessWidget {
#override
Widget build(BuildContext context) {
final SessionListCubit cubit = context.read<SessionListCubit>();
return ListView.separated(
itemBuilder: (context, index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: GestureDetector(
onTap: () {
print('Tapped on time section. ');
},
child: Padding(
padding: EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('10:30 Am'),
Icon(Icons.expand_more),
],
),
),
),
),
Expanded(
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
print("item builder.");
return CollapsibleAgendaItem(
session: cubit.state.sessions[index], isLiked: true);
},
separatorBuilder: (context, index) {
return const Divider(
color: Colors.grey,
);
},
itemCount: 2),
),
],
);
},
separatorBuilder: (context, index) {
return const Divider(
color: Colors.grey,
);
},
itemCount: 4);
}
}
Edit: I'm going to explain the reason for this problem in my case. Maybe it will help someone in the future. When Flutter builds the child it asks for the required width/height from the parent. But as I used a ListView as a child, it doesn't know the height instantly. So, the Column was taking only the height it needed. But, I experimented that providing a height for the ListView solve the problem. But, In my case, I can't determine the height in runtime, instead, I used a Column like the accepted answer, which solved my problem. In the future, If someone finds a solution with ListView, Please do comment here.
You can top Row with IntrinsicHeight(expensive-widget). Also while you are not using scrollable physics, you can replace listVIew with Column
return ListView.separated(
itemBuilder: (context, index) {
return IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ColoredBox(
color: Colors.red,
child: GestureDetector(
onTap: () {
print('Tapped on time section. ');
},
child: Padding(
padding: EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('10:30 Am'),
Icon(Icons.expand_more),
],
),
),
),
),
Column(
children: List.generate(2, (index) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 200,
),
const Divider(
color: Colors.grey,
),
],
);
}),
),
],
),
);
},
separatorBuilder: (context, index) {
return const Divider(
color: Colors.grey,
);
},
itemCount: 4);
Use mainAxisSize:MainAxisSize.max inside column.
To make the contents of the Row fill the entire height, you need to set crossAxisAlignment: CrossAxisAlignment.stretch, on the Row.
Here is the documentation on CrossAxisAlignment vs MainAxisAlignment.
CrossAxisAlignment.stretch
Stretches children across the cross axis. (Top-to-bottom for Row, left-to-right for Column)

In Flutter, how to make each Row child as big as the biggest child?

I am trying to find a way to have two ListView as children of a Row to have matching heights. This means if one ListView is shorter than the other one, then it must stretch until it matches the other ListView's height.
Schematically speaking this is what I have now:
How can I have the green ListView to match the orange's ListView's height?
This is my row at the moment:
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildList(list: listA), // returns a `ListView` wrapped in `Card`
_buildList(list: listB),
],
)
I tried setting crossAxisAlignment to CrossAxisAlignment.strech but it causes an error with this message:
A RenderBox object must have an explicit size before it can be hit-tested. Make sure that the RenderBox in question sets its size during layout.
I believe it means that one of the child can't ascertain its height...
After following pskink suggestion to use IntrisictHeight, albeit very expensive as per the documentation, I managed to make it work by replacing my ListViews with Columns.
Indeed, as my list are quite short (as explained in my OP), there is no need for ListView with scrolls, animation, recycling, etc.
Using Column was also the only way to make it work with IntrisictHeight anyway.
So this is the solution I retained:
IntrinsicHeight(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildList(listA),
_buildList(listB),
],
),
Widget _buildList(List<String> list) {
return Container(
width: 400,
margin: const EdgeInsets.only(left: 8, right: 8),
child: Card(
elevation: 10,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SkillType(name: skillType),
for (final Text in list) Text(text),
],
),
);
}
All thanks go to pskink.
You can wrap your ListView with Expanded widget
_buildList({required List<String> list}) {
return Expanded(
child: Container(
color: Colors.cyanAccent,
child: ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) => ListTile(
title: Text(list[index]),
),
),
),
);
}
Also provide ScrollController to listview to avoid getting error
class Home extends StatefulWidget {
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<String> listData = List.generate(10, (index) => '$index');
List<String> listData2 = List.generate(5, (index) => '$index');
List<TextEditingController> listDataCTL = [];
ScrollController controler = new ScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AppBar'),
),
body: SingleChildScrollView(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Card(
color: Colors.yellow,
child: ListView.builder(
controller: controler,
itemCount: listData.length,
shrinkWrap: true,
itemExtent: (listData.length < listData2.length) ? listData2.length / listData.length * 50 : 50,
itemBuilder: (context, index) {
return Container(
color: Colors.blue,
margin: EdgeInsets.symmetric(vertical: 4),
child: Text('Item'),
);
},
),
),
),
Flexible(
child: Card(
color: Colors.green,
child: ListView.builder(
controller: controler,
itemCount: listData2.length,
itemExtent: (listData.length > listData2.length) ? listData.length / listData2.length * 50 : 50,
shrinkWrap: true,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.symmetric(vertical: 4),
color: Colors.red,
child: Text('Item'),
);
},
),
),
),
],
),
));
}
}
When listData.length> listData2.length
When listData2.length> listData.length
UPDATE:
Scaffold(
appBar: AppBar(
title: Text('AppBar'),
),
body: Card(
child: Row(
children: [
_buildList(list: listData, compareList: listData2),
_buildList(list: listData2, compareList: listData),
],
),
),
)
_buildList({List<String> list, List<String> compareList, double itemExtent = 50, double spacing = 8}) {
return Flexible(
child: GridView.builder(
shrinkWrap: true,
padding: EdgeInsets.all(0),
physics: const NeverScrollableScrollPhysics(),
itemCount: list.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
crossAxisSpacing: 0,
mainAxisSpacing:
(list.length < compareList.length) ? (((compareList.length - list.length) * itemExtent) + ((compareList.length - 1) * spacing)) / 4 : spacing,
mainAxisExtent: itemExtent,
),
itemBuilder: (context, index) {
return Container(
color: Colors.red.withOpacity(0.3 + ((index * 5) / 100)),
margin: EdgeInsets.symmetric(vertical: 0),
child: Text('Item'),
);
},
),
);
}
I think you want this version:
_buildList({List<String> list, List<String> compareList, double itemExtent = 50, double spacing = 8}) {
return Flexible(
child: Card(
child: ListView.builder(
itemCount: list.length,
shrinkWrap: true,
padding: EdgeInsets.only(
bottom: (list.length < compareList.length) ? (((compareList.length - list.length) * itemExtent) + ((compareList.length - 1) * 0)) : 0,
),
physics: const NeverScrollableScrollPhysics(),
itemExtent: itemExtent,
itemBuilder: (context, index) {
return Container(
color: Colors.red.withOpacity((index * 5) / 100),
margin: EdgeInsets.symmetric(vertical: 0),
child: Text('Item'),
);
},
),
),
);
}

How to put two parallel flutter lists in a column

I wanna put two parallel lists inside the row marked in the picture. I've tried different types of structures, but all give me problems.
Each element of the dynamic lists will have a picture and a Text.
Screen
I've been trying to put two sized box inside the row:
Row(children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.5,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.5,
),
])
After the width putting the ListView
Everything I try gives me the next error:
error
Please refer to below code
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
ListView.builder(
physics:
NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 15,
itemBuilder: (BuildContext context,
int index) {
return ListTile(
leading: Icon(
Icons.star_border,
),
title: Text(
'List $index' +
"Lorem ipsum dolor sit amet, ",
),
);
},
),
],
),
),
Flexible(
child: Padding(
padding: EdgeInsets.only(left: 15.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
ListView.builder(
physics:
NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 15,
itemBuilder: (BuildContext context,
int index) {
return Row(
children: [
Icon(
Icons.star,
),
Text("List item $index"),
],
);
},
),
],
),
),
)
],
),
Try this :
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ListView.builder(
shrinkWrap:true,
builder:(context,index)=> Container(),
),
ListView.builder(
shrinkWrap:true,
builder:(context,index)=> Container(),
),
],
);

Flutter Horizontal ListView.builder() not working

I'm making an API call, which gives back a list. I want to display, let's say, SomeWidget() horizontally using ListView.builder(). But I'm not able to achieve that target still.
I've tried wrapping the ListView.builder() inside a Container() and gave it some height and width. But hard luck. I've also tried wrapping it using Expanded(). But hard luck. I have tried wrapping it with, first a Container() then wrapping the Container() with Expanded(). Still hard luck.
The error that I keep getting is the following:
'package:flutter/src/rendering/sliver_multi_box_adaptor.dart': Failed assertion: line 544 pos 12: 'child.hasSize': is not true.
The relevant error-causing widget was
ListView
Here's my ListView.builder() code:
someList.isNotEmpty ?
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Column(
children: [
Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
child: Text('HEADING',
style: TextStyle(fontSize: 12),
),
),
),
Container(
height: 15.h,
width: 80.w,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: someList.length,
itemBuilder: (_, index) {
return SomeWidget();
}),
),
],
),
),
) : Text(''),
Is there any other information that I should add here?
Update
The ListView.builder() is working perfectly for vertical scrolling. The code is the following:
Container(
height: 15.h,
width: double.infinity,
child: ListView.builder(
// scrollDirection: Axis.horizontal,
itemCount: someList.length,
itemBuilder: (_, index) {
return SomeWidget();
},
),
),
On adding scrollDirection: Axis.horizontal, it however doesn't work.
Add Your ListView.builder() inside Expanded or Flex Widget hope its help to you
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child:ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: someList.length,
itemBuilder: (_, index) {
return SomeWidget();
}),
),
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
//Try add height and width
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
textDirection : TextDirection.ltr or rtl
children: [
Align(
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
child: Text('HEADING',
style: TextStyle(fontSize: 12),
),
),
i think or maybe the listview not the problem maybe inside on the listview item is the problem which as the error you've shown is that need has szie so in between in those item need to be wrap container or sized box
If Expanded or Flexible widgets didn't work, try giving a fixed width to your ListView items.
Column(
children: [
child:ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: someList.length,
itemBuilder: (_, index) {
return Container(width: 150, child: someWidget());
}),
),
],
),

Fixed number of rows, with horizontal scroll for chips

I have tried using gridlist, flutter staggered grid view but was still not able to do it.
This is what I would like it to look like.
Intended look
The best i could do is this, but the problem is that if i use a grid it gives each element same width.
GridView.builder(
scrollDirection: Axis.horizontal,
itemCount: widget.profile.interests.length,
gridDelegate:
new SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 50,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 0.3),
itemBuilder: (BuildContext context, int index) {
return CardWidget(context, index);
})
output
You can use ListView instead...
ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(
children: <Widget>[
CardWidget('Long text'),
CardWidget('Even longer text'),
CardWidget('Long text'),
CardWidget('Even longer text'),
CardWidget('Text'),
],
),
Row(
children: <Widget>[
CardWidget('Text'),
CardWidget('Short text'),
CardWidget('Text'),
CardWidget('Short text'),
CardWidget('Long text'),
CardWidget('Even longer text'),
],
)
])
],
);
OUTPUT
UPDATE WITH BUILDER
Here is another solution with builder. There is probably better solution for this. It just a first one what cross my mind
List<String> list = ['Long text', 'Even longer text', 'Long text', 'Even longer text', 'Text', 'Text', 'Short text', 'Text', 'Short text', 'Long text', 'Even longer text'];
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 50,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: list.length,
itemBuilder: (context, index) {
return index.isEven ? CardWidget(list[index]) : Container();
},
),
),
SizedBox(
height: 50,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: list.length,
itemBuilder: (context, index) {
return index.isOdd ? CardWidget(list[index]) : Container();
},
),
)
],
),
)
List<int> topRow = [];
List<int> bottomRow = [];
List<int> _addRowElements() {
for (int i = 0; i < numbers.length; i++) {
if (i.isEven) {
topRow.add(numbers[i]);
} else {
bottomRow.add(numbers[i]);
}
}
return topRow;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: MediaQuery.of(context).size.width,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
children: <Widget>[
..._addRowElements()
.map(
(number) => TextAndIconWidget(
label: number.toString(),
),
)
.toList(),
],
),
Row(
children: <Widget>[
...bottomRow
.map(
(number) => TextAndIconWidget(
label: number.toString(),
),
)
.toList(),
],
),
],
),
],
),