How to dynamically increase the height of a list and scroll after reaching a max height? - flutter

Hi i'm trying to create a list view to which i dynamically add items in the app. I want the height of the list to grow till it reaches a defined max height, and then show a scrollbar when it overflows.
_buildPage() {
return Container(
width: widget.width,
constraints: BoxConstraints(
maxHeight: widget.maxHeight,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildTitle(),
_buildAddBump(),
ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => _buildItem(_items[index]),
),
],
),
);
}
This is of course not working since a ListView needs a pre-defined height. But wrapping the ListView in an Expanded will take all the remaining height and the ListView will be at maxHeight even when the items in it don't need the height.
I've tried putting the items in a SingleChildScrollView with a Column as a child, but that doesn't seem to do anything. The widget just overflows when i add more items than the height can hold. Is there a way to give a max height constraint to a list and tell it to take the height of its contents till it reaches the max height?
UPDATE:
I tried to add shrinkWrap: true to the ListView, but it's still not working. The page renders without errors and the ListView height increases as items are added, but when the height goes beyond maxHeight, it just overflows instead of adding a scroll.

Seems like I found the solution and it's quite easy. Wrap your ListView inside Flexible and set shrinkWrap to true:
...
Flexible(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => _buildItem(_items[index]),
shrinkWrap: true,
)
),
...
If it's not the solution, let me know what is wrong in comments.

Related

ListView Builder vs SingleChildScrollView + Row combo

I want to make a responsive UI in my app. In my home page, I have ScrollView->Column->childrens structure.
When I want to use horizontal Listview.builder in my column, it throws me error because height is unbounded. I fix it with wrapping my listview builder with container and I give this container a height value. But I dont want to give this height hardcoded,
I want to make my container height to its own child's height. I searched it and found a solution like ScrollView->Row->list.generate instead of ListView.Builder. But is it okay to make it like this? Does it cause performance problems or is it a bad practice?
My list isn't big. It has max 20 elements
For example this throwing error:
#override Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
itemCount: 5,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemBuilder: (context, index) {
return Container(
width: 200,
height: 200,
);
},
)
],
)),
); }
But this does not throw an error
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(
5,
(index) => Container(
width: 200,
height: 200,
color: Colors.red,
)),
),
),
],
));
}
To clarify for everyone:
ListViews generate their children "lazily" - meaning they won't be drawn until they have been shown on screen. This means, of course, that they do not know the height of their items.
For example, if the ListViews children have heights similar to this list:
ooooOooo
But only this part was shown on the screen:
oooo | Oooo
And the ListView set it's height to fit "o" then it wouldn't be able to fit "O" when it eventually came on screen.
On the other hand, Rows do draw all of their children on spawn, meaning that, while they do know the size for all their widgets, they can become very slow quickly.
I would not suggest using images inside rows with 2-digit+ children, as they can be laggy not only on their initial draw, but also while the user does other things in the page containing said row - things such as scrolling up/down the page.
While testing I found that a row with just 30 children of the same stack (a small AssetImage on top of an IconImage) would lag the entire page when just scrolling up/down - not even scrolling along the row itself.
My recommended solution for you Ahmet, even though you don't want to hard-code your ListView's height, is to settle and set your ListView's height using:
MediaQuery.of(context).size.height * (percentage of screen's height you'd like for the ListView to take).

What is the right way to handle spaces between Column when dynamically adding children or passing a List? - Flutter

I've seen a lot of posts on how to add spacing between children's Columns using SizedBox.
How do you go about spacing when you are dynamically adding children or just providing a List to the Column()?
A few thoughts I have in mind but not sure if it's the right way to do this:
When you dynamically generate a child widget wrap the child in padding or container with margin.
Create a function to automatically insert a SizedBox after generating a child
Does it mean Wrap() is the right way to go instead of Column()?
What are your go-to ways of handling this?
when your children are generated dynamically you should use ListView.seperator
ListView.separated(
separatorBuilder: (context, index) => SizedBox(
height: 10,
),
itemCount: 20,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.all(8.0),
child: Center(child: Text("Index $index")),
),
)
and separatorBuilder add height or width between your widgets

Two ListViews in the same column - Vertical viewport was given unbounded height [duplicate]

This is my code:
#override
Widget build(BuildContext context) {
return new Material(
color: Colors.deepPurpleAccent,
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children:<Widget>[new GridView.count(crossAxisCount: _column,children: new List.generate(_row*_column, (index) {
return new Center(
child: new CellWidget()
);
}),)]
)
);
}
Exception as follows:
I/flutter ( 9925): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 9925): The following assertion was thrown during performResize():
I/flutter ( 9925): Vertical viewport was given unbounded height.
I/flutter ( 9925): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter ( 9925): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter ( 9925): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter ( 9925): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter ( 9925): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter ( 9925): instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
I/flutter ( 9925): the height of the viewport to the sum of the heights of its children.
I/flutter ( 9925):
I/flutter ( 9925): When the exception was thrown, this was the stack:
I/flutter ( 9925): #0 RenderViewport.performResize.<anonymous closure> (package:flutter/src/rendering/viewport.dart:827:15)
I/flutter ( 9925): #1 RenderViewport.performResize (package:flutter/src/rendering/viewport.dart:880:6)
I/flutter ( 9925): #2 RenderObject.layout (package:flutter/src/rendering/object.dart:1555:9)
Adding this two lines
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
...
This generally happens when you try to use a ListView/GridView inside a Column, there are many ways of solving it, I am listing few here.
Wrap ListView in Expanded
Column(
children: <Widget>[
Expanded( // wrap in Expanded
child: ListView(...),
),
],
)
Wrap ListView in SizedBox and give a bounded height
Column(
children: <Widget>[
SizedBox(
height: 400, // fixed height
child: ListView(...),
),
],
)
Use shrinkWrap: true in ListView.
Column(
children: <Widget>[
ListView(
shrinkWrap: true, // use this
),
],
)
So, everyone posted answers but no one cared to explain why:
I'll copy the documentation about shrinkWrap:
Whether the extent of the scroll view in the [scrollDirection] should be
determined by the contents being viewed.
If the scroll view does not shrink wrap, then the scroll view will expand
to the maximum allowed size in the [scrollDirection]. If the scroll view
has unbounded constraints in the [scrollDirection], then [shrinkWrap] must
be true.
Shrink wrapping the content of the scroll view is significantly more
expensive than expanding to the maximum allowed size because the content
can expand and contract during scrolling, which means the size of the
scroll view needs to be recomputed whenever the scroll position changes.
Defaults to false.
So, taking the vocabulary of the docs, what's happening here is that our ListView is in a situation of unbounded constraints (in the direction that we are scrolling), thus the ListView will complain that:
... a vertical viewport was given an unlimited amount of vertical space in
which to expand.
By simply setting shrinkWrap to true will make sure that it wraps its size defined by the contents. A sample code to illustrate:
// ...
ListView(
// Says to the `ListView` that it must wrap its
// height (if it's vertical) and width (if it's horizontal).
shrinkWrap: true,
),
// ...
That's what is going with your code, nonetheless, #Rémi suggestion is the best for this case, using Align instead of a Column.
put grid view inside Flexible or Expanded widget
return new Material(
color: Colors.deepPurpleAccent,
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children:<Widget>[
Flexible(
child: GridView.count(crossAxisCount: _column,children: new List.generate(_row*_column, (index) {
return new Center(
child: new CellWidget(),
);
}),))]
)
);
Although shrinkWrap do the thing, but you can't scroll in ListView.
If you want scrolling functionality, you can add physics property:
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: ScrollPhysics(),
...
Cause of Viewport Exception
GridView / ListView grow until constraints (limits) stop this expansion & then scroll to view items beyond that size.
But Column lays out its children without any constraints, completely ignoring its own size and screen size. There is infinite space inside of Column.
Viewport was given unbounded height exception occurs because Grid/ListView expands to infinity inside a Column.
Flexible/Expanded exist to give Column children constraints based Column's size. (These two widgets cannot be used anywhere else, except inside Row and Column.)
Inside a Flexible/Expanded widget in Column, Grid/ListView only uses remaining space not taken by other, non-flexible widgets, which get laid out first. (See bottom for layout phases info.)
shrinkWrap is not a good solution
Using shrinkWrap: true on ListView inside a Column isn't really helpful as:
ListView no longer scrolls
ListView can still overflow
A ListView tall enough to show all of its items, will not scroll. Arguably defeats the purpose of using a ScrollView widget (parent class of ListView).
In Column layout phase 1 (see bottom for explanation of layout phases), ListView can be any height it wants (there are no constraints).
A ListView with shrinkWrap:true will grow in height to show all its items.
With enough items, a shrinkWrapped ListView will grow & grow (it never scrolls) to overflow whatever Column is inside, be it screen or other tighter constraint.
Shouldn't shrinkWrap just make ListView only as big as its items OR remaining space (up to screen height), and then scroll?
That would make intuitive sense, but inside a Column in phase 1 layout is done in unbounded space.
So, remaining space is unlimited/infinite. A max height is never reached. shrinkWrap:true just keeps growing Column height as items are added until overflowing the screen (or other smaller constraint).
Example shrinkWrap:true Overflow
Here's an example of adding items to a shrinkwrapped ListView in a Column until its height is taller than the screen, creating an overflow warning area:
(just keep pressing the + sign Floating Action Button)
import 'package:flutter/material.dart';
class ColumnListViewPage extends StatefulWidget {
#override
_ColumnListViewPageState createState() => _ColumnListViewPageState();
}
class _ColumnListViewPageState extends State<ColumnListViewPage> {
int _count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Column & ListView'),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
_count++;
});
},
),
body: SafeArea(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.red)
),
/// // without center, Column will be as narrow as possible
alignment: Alignment.center,
/// Column is forced (constrained) to match screen dimension,
child: Column(
children: [
Text('Inner (Nested) Column'),
ListView.separated( // ← should be wrapped in Expanded
itemCount: _count,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.symmetric(vertical: 38.0),
child: Text('Row $index'),
),
shrinkWrap: true, // should be removed
)
],
),
),
),
);
}
}
For GridView/ListView in a Column, wrapping within Expanded or Flexible is likely the safest solution.
Expanded / Flexible
Expanded and Flexible can only be used inside Column (and its siblings like Row, etc.).
They must be used as immediate children of Column. We can't "nest" Expanded/Flexible in SizedBox or other widgets. Only directly under Column as immediate children.
Inside Column, placing ListView/GridView in Expanded or Flexible ensures it will use only available space, rather than infinite space.
Column
→ Expanded
→ ListView
ListView/GridView inside Expanded/Flexible in a Column doesn't need shrinkWrap because it is constrained (given a max. height) by the Expanded/Flexible widget. So when that constrained/defined height is used up, ListView/GridView will stop growing & begin scrolling.
Column Layout Phases
Column lays out children in 2 phases:
1st without constraints (unbounded space)
2nd with remaining space based on Column's parent
Phase 1
Any Column child not inside Flexible or Expanded will be laid out in phase 1, in infinite, limitless space completely ignoring screen size.
Widgets that need a vertical boundary/constraint to limit their growth (e.g. ListView) will cause a Vertical viewport was given unbounded height exception in phase 1 as there are no vertical bounds in unbounded space.
Most widgets have intrinsic size. Text for example, is only as high as its content & font size/style. It doesn't try to grow to fill a space. Defined-height widgets play well in Phase 1 of Column layout.
Phase 2
Any widget that grows to fill bounds, should be put in a Flexible or Expanded for Phase 2 layout.
Phase 2 calculates Column's remaining space from its parent constraints minus space used in Phase 1. This remaining space is provided to Phase 2 children as constraints.
ListView for example, will only grow to use remaining space & stop, avoiding viewport exceptions.
More info on Column layout algorithm and how Expanded works within it.
This situation typically happens when a scrollable widget is nested inside another scrollable widget.
In my case i use GridView inside of Column and that error throws.
GridView widget has shrinkWrap property/named parameter, to set it true my problem is fixed.
GridView.count(
shrinkWrap: true,
// rest of code
)
Don't use Column for single child alignment. Use Align instead.
new Align(
alignment: Alignment.topCenter,
child: new GridView(),
)
There are a lot of answer but each one have its own limitation. everyone is saying use shrinkWrap: true. Yes you should use it but the effect of these code is when you will scroll the list, it will automatically move to top item, means scroll back to top.
So perfect solution is use shrinkwrap with physics then only the list will be able to scroll smoothly.
shrinkWrap: true, //
physics: ScrollPhysics(), // both line is mandatory
If you want to see other example of Listview.builder with column as a child and list can be scroll vertically then you can see the example here - arrange multiple text in a vertical list.
Adding this two lines
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
...
I wanted to post another answer because many answers (including the accepted one!) suggest using shrinkWrap: true in ListView.builder. While this removes the error, this solution is NOT ideal for performance reasons for this case. Flutter designed ListView with infinite size specifically to discourage this behaviour only when necessary!
Shrink wrapping the content of the scroll view is significantly more expensive than expanding to the maximum allowed size because the content can expand and contract during scrolling, which means the size of the scroll view needs to be recomputed whenever the scroll position changes.
In some cases/devices, this property caused glitchy scrolls and animation lag in my projects. Even if the lag isn't noticeable, this property does hinder scroll performance.
The OP shows that he is creating a small (finite) number of children for ListView. If we want to resolve the error for these lists, Google recommends using a Flexible widget, i.e. Expanded (a Flexible with flex 1) on the list itself.
Expanded(
child: ListView(
...
),
)
There are other layout issues here, but the vertical viewport unbounded height is because the ListView doesn't have a parent that bounds its size.
Two ways which we can solve the above issues
1. Using Flexible Widget,
The Flexible widget can be used inside Row, Column, and Flex.The flexibility to expand to fill the available space in the main axis (e.g., horizontally for a [Row] or vertically for a [Column])
But, remember, unlike Expanded, The Flexible widget does not require the child to fill available space. So, it's always better to use Flexible.
Column(
children: <Widget>[
Flexible(
child: ListView(...),
),
],
)
2. Using shrinkWrap: true
Using shrinkWrap as true, we tell the Listview to render its List to available space, not take below remaining screen.
Column(
children: <Widget>[
ListView(
shrinkWrap: true,
),
],
)
Also, physics attribute can be used to allow/disallow scrolling of Listview
Stop Scrolling
ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
),
Allow Scrolling
ListView(
shrinkWrap: true,
physics: ScrollPhysics(),
),
I faced this problem because I wanted to display a GridView inside a ListView, and the solution was by adding the following not only in the ListView but also in the GridView as well:
shrinkwrap: true
scrollDirection: Axis.vertical
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 100,
width: double.infinity,
color: Colors.pinkAccent,
),
);
},
),
If you still face this problem in 2021 ..here is the best and easy solution
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
// itemExtent: 400, use this if you give hight of your items
physics: ScrollPhysics(),)
their are many solution .. but they have problem.. like when we use ListView and use its property shrinkWrap .then one thing you many notice the scroll does not work on listView so you must use physics: ScrollPhysics()
Container(
height: 100,
child: ListView.builder(
scrollDirection: Axis.vertical,
physics: BouncingScrollPhysics(),
shrinkWrap: true,
itemCount: 5,
itemBuilder: (context, index) {
return Text("Hello World $index");
},
),
);
test this one for second listview
ListView.builder(
physics: NeverScrollableScrollPhysics(),
Just want to add my solution that worked in my case where I have vertical list view together with other buttons and texts on the same screen:
Flexible(
child:
ListView.builder(
// make sure to add the following lines:
shrinkWrap: true,
physics: ScrollPhysics(),
// the rest of your list view code
) // ListView
) // Flexible
This solution works well particularly when you have other widgets on the SAME SCREEN. In this particular case, "Expand" or "Flexible" alone will not work.
There is a widget called Center. It should help you achive what you want to do (center it vertically). Official Documentation for Center widget
Also meet this error, because the code like this
return Container(
child: Column(
children: [
ListView.separated(
itemBuilder: (context, index) {
return CircleManagerSettingItem(widget.membersInfo[index]);
},
separatorBuilder: (_, index) => DD(
height: 1,
paddingLeft: 10,
paddingRight: 10,
),
itemCount: (widget.membersInfo.length)),
],
),
);
remove the Column inside the Container then Ok.
in my case, I had to wrap the ListView inside a Container and give this container a specific Height
I have made a custom function to solve your problem.
You can directly use this function in your code base:
Widget horizontalListView(height, width, color, child, margin) {
return Column(
children: [
SizedBox(
height: 200,
child: ListView.builder(
itemCount: 10,
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemBuilder: (context,index) {
return Container(
height: height,
width: width,
margin: EdgeInsets.all(margin),
color: color,
child: child
);
}
),
),
],
);
})
P.S. Sorry for bad alignment of code.
You can put Expanded() as a parent of that widget too.
if you are using CustomScrollView
you can try SliverFillRemaining

How to make a ROW scrollable left and right?

Is it possible to make children of a Row widget display only X amount of children and make the widget scrollable?
Here's an example screenshot
Any suggestions?
You can put your listview inside a SizedBox widget with specific height, then you should configure your list to make it scroll in a horizontal way:
SizedBox(
width: MediaQuery.of(context).size.width // This will make your list fill the screen horizontally
height: 100.0 // you can edit it as you desire but it should be small to make the effect of scroll-able row
child: listview.builder(
scrollDirection: Axis.horizontal, //This will make your list scroll on the horizontal axis
itemCount: 10.0,
itemBuilder: (BuildContext content, int index) {
// Your itemBuilder code here
}
),
),

How do I implement this in Flutter?

I have tried BottomNavigationBar but it requires an Icon.
This instead is text only, sort of represents analytics history and it's scrollable.
For the bottom bar you could make your own custom widget like this:
Container(
height: 80.0,
child: ListView(
scrollDirection: Axis.horizontal,
children: List.generate(10, (int index) {
return new Text("$index");
}),
),
),
Replace the children with actual children that you want for the ListView and adjust its wrapping to have a height that suits your need and there you go