Nested Listview not scrolling horizontally - flutter

I have a relatively complex Listview setup, where one Listview acts as a scrolling parent of a horizontal Listview, which acts as a parent to a third vertical Listview.
Here is an image of the general idea of the layout: https://cdn.discordapp.com/attachments/752981111738466467/895739370227773480/IMG_20211007_142732.jpg.
I'm having trouble getting the middle Listview, the horizontal Listview (marked 2 in the image), to scroll.
ConstrainedBox(
constraints: BoxConstraints(...),
child: ListView.builder( // This is Listview 1
controller: ScrollController(),
itemCount: itemCount,
itemBuilder: (context, worldIndex) {
return ConstrainedBox(
constraints: BoxConstraints(...),
child: ListView.builder( // This is Listview 2
controller: ScrollController(),
itemCount: ...,
scrollDirection: Axis
.horizontal, // grows horizontally to show the hierarchy of one card (the card selected in the hierarchy above it, or for the first level, the world) to its children
itemBuilder: (context, index) {
...
return ConstrainedBox(
constraints: BoxConstraints(...),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
decoration: ...,
),
child: ListView.builder( // This is Listview 3
controller: ScrollController(),
itemCount: getNumChildren(index),
itemBuilder: (context, index2) {
return ...;
}),
)));
}));
}));
For brevity, I have removed several parts of my code and replaced them with elipses. I don't think it is likely that any of these areas could cause any issues with the Listview, but please let me know if they could.
Edit: The code I have already works properly, aside from the horizontal Listview not scrolling. My solution needs to have a dynamically expandable Listview at each level of the tree (1, 2, and 3, in the image, for example). The primary target platform for this is Windows.
I'm assuming the issue involves a problem with which Listview wins the GestureArena, but I am currently at a loss in how to work around that issue, providing a way to scroll all of available Listviews.
Thank you in advance, and I hope you have a great day!

As I could understand it. Maybe you are looking for something like this? I have commented the 3 types of scrolls you need. If anything is not as expected, please mention.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
for (var i = 0; i < 5; i++)
// One big section in the largest col (1).
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 200.0,
child: ListView.builder(
// Horizontal item builder. (2)
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (context, index) {
return SingleChildScrollView(
child: Column(
// Inside one vertical section. (3)
children: [
for (var i = 0; i < 5; i++)
ElevatedButton(
onPressed: () {}, child: Text("OK")),
],
),
);
},
),
),
],
),
],
),
),
),
),
);
}

The solution to this turned out to be that Flutter has intentionally turned off the ability to scroll horizontally using both scroll wheel and dragging for Windows (or at least, that's what seems to be the case according to what I was able to find in this issue).
To solve that, they have made a migration guide here.
Following the guide, it is extremely simple to override the drag devices used in order to re-enable the intended drag scrolling. This still does not enable scrolling horizontally with a mouse wheel, but for my code that is not an issue.
class CustomScrollBehavior extends MaterialScrollBehavior { // A custom scroll behavior allows setting whichever input device types that we want, and in this case, we want mouse and touch support.
#override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
};
}
Then, in the actual Listview's code, we set a ScrollConfiguration with this new class as its behavior.
ScrollConfiguration(
behavior: CustomScrollBehavior(),
child: Listview.Builder(
controller: ScrollController(),
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
...
})

Related

Flutter: unexpected space at the top of ListView

I have the following source code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: scrollController,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(context, cardIndex) {
return Container(
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Main Course',
style: kRestaurantMenuTypeStyle,
),
ListView.builder(
itemCount: menuCards[cardIndex].menuItems.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
itemBuilder: (context, itemIndex) {
return RestaurantMenuItem(
menuItem: menuCards[cardIndex].menuItems[itemIndex],
);
},
),
],
),
);
},
childCount: menuCards.length,
),
),
],
),
);
}
Unfortunately, the ListView.builder() creates this extra space on top automatically. This is shown in the image below. That is the big white space between the 'Main Course' and 'Pancit Malabon' texts.
I don't understand why ListView does that. How do I remove the space?
To avoid this behaviour of listview, override padding property with a zero [padding]
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: data.items.length,
itemBuilder: (context, index) {}
);
Looking at your screenshot, the ListView scrolls close to the top of the screen and by default, ListView adds a padding to avoid obstructing the system UI. So a zero padding would remove the extra space.
By default, ListView will automatically pad the list's scrollable
extremities to avoid partial obstructions indicated by MediaQuery's
padding. To avoid this behavior, override with a zero padding
property.
Source : ListView
I solved the issue by adding a padding to my list view like so:
ListView.builder(
padding: EdgeInsets.only(top: 0.0),
...
),
I don't understand why the solution works. If someone can explain the bug, I can accept theirs as the correct answer.
You can wrap your listview with MediaQuery.removePadding
MediaQuery.removePadding(
context: context,
removeTop: true,
child: ListView(...),
)

Set Containers array into Stack view Flutter

I am trying to display a ListView Horizontal n times in Stack or Row. But in the end all the ListViews are piled up instead shows one by one from top to bottom.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Prueba para trabajar en el proyecto"),
),
body: Stack(
children: <Widget>[
_crearLista(),
_crearLista(),
_crearLista(),
_crearLista(),
_crearLista(),
//_crearLoading(),
],
),
);
}
Widget _crearLista() {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.25,
decoration: BoxDecoration(),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _listaNumeros.length,
itemBuilder: (BuildContext context, int index) {
//return image
},
controller: _scrollController,
),
),
)
],
),
);
}
Any suggestions? please. I want to show all the LsitViews.
A Stack widget puts its children above each other (overlapped or piled up) at the left top corner of the stack by default. Unless you position each child using a Positioned or Align widget. To have all the children one below the other (starting from top of the screen towards bottom), you need to use a Column instead of Stack.
Stack children behave like layers, one covers second, etc.
If you want one child exactly below another, there is ListView and Column

ListView inside Column throws error 'Vertical viewport was given unbounded height'

My question seems to be a duplicate of ListView inside Column causes 'Vertical viewport was given unbounded height' but the solution to add an Expanded or Flexible widget around the ListView doesn't work at all. It still throws the same error: Vertical viewport was given unbounded height.
var data = ['a','b','c','d'];
Column(
children: <Widget>[
ListView.builder(
itemCount: data.length,
itemBuilder: (ctx, i) {
return Row(
children: <Widget>[
Text(data[i], style: TextStyle(fontSize: 24 * Rat.rat, color: Colors.white)),
],
);
},
),
],
);
Obviously it can easily be fixed by adding a container around the ListView with a fixed height but there should be no need for that, I want it to be dynamically sized.
UPDATE:
Try something like this:
class Esempio1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("WAKAWAKA"),
),
body: Container(
child: Column(
children: <Widget>[
Text("eg1"),
Text("eg2"),
Text("eg3"),
Expanded(
child: ListView.builder(
itemCount: 20,
itemBuilder: (ctx,i){
return ListTile(title: Text("aaaaaa$i"),);
},
),
)
],
),
),
);
}
}
I just tried this code and it should do what you want.
The OP actually had the block of code posted contained inside another Column, that's why the Expanded wasn't working as it should when wrapped around the ListView. Wrapping the Column inside another Expanded solved the problem and made it all work as expected.
How about wrapping ListView builder in SingleChildScrollView and setting up its physics to
physics: NeverScrollableScrollPhysics(),

Refresh indicator doesn't work when list doesn't fill the whole page

I have a page the has a list with refresh indicator. When I have many elements in the list (filling the whole view and more, i.e. I can scroll), the refresh indicator works.
However, when I have only one or two elements in the list (nothing to scroll), then the refresh indicator doesn't work. Is this the expected behaviour? Is there a way to get the indicator to work also for short lists?
I tried wrapping the list view (child of refresh indicator) with a scroll bar but the problem still persists.
Future<void> _onRefresh() async {
_getPeople();
}
#override
Widget build(BuildContext context) {
super.build(context);
return new Container(
child: _items.length == 0
? Center(child: CircularProgressIndicator())
: new RefreshIndicator(
onRefresh: _onRefresh,
child: Scrollbar(
child: new ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return PeopleCard(
People: _items[index],
source: 2,
);
},
itemCount: _items.length,
),
)),
);
}
If the length of the items list is 1 or 2 or any amount that doesn't need scrolling, refresh indicator should also work.
Set the physics to AlwaysScrollableScrollPhysics just like the below code.
new RefreshIndicator(
key: _refreshIndicatorKey,
color: Colors.blue,
onRefresh: (){
setState(() {
_future = fetchPosts(http.Client());
_getAvalailableSlots();
});
},
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
itemCount: data.length,
itemBuilder: (context, i) {
//do something
}
)
)
Case 1:
Use AlwaysScrollableScrollPhysics() in your Listview or SingleChildScrollView.
physics: const AlwaysScrollableScrollPhysics(),
Case 2:
If you want to use other scroll physics, use this way...
physics: BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
Case 3:
Make sure you didn't make shrinkWrap, true. ShrinkWrap has to be false like default.
shrinkWrap: true. ❌
shrinkWrap: false. ✅
shrinkWrap: false,
Case 4:
If you have any custom widgets other than a listview, use stack and listview this way...
RefreshIndicator(
onRefresh: () async {
print('refreshing');
},
Stack(
children: [
YourWidget(),
ListView(
physics: const AlwaysScrollableScrollPhysics(),
),
],
),
),
To use with BouncingScrollPhysics:
RefreshIndicator(
onRefresh: () async {
},
child: ListView.builder(
physics: BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
itemBuilder: (context, index) {
return Text("${index}");
}),
To make RefreshIndicator always appear, set physics: const AlwaysScrollableScrollPhysics() for the scrollable child like below.
ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <Widget>[widget.bodyWidget]
)
If you still have some errors, the below might help.
ListView widget needs to have height value to be "always" scrollable vertically. So wrap it with SizedBox or some other proper widget and set the height.
How to fix the height? Get the height of viewport using MediaQuery.of(context).size.height and, if needed, subtract the value of non-scrollable part.
If you use SingleChildScrollView and it's not working with AlwaysScrollableScrollPhysics(), it is because the child doesn't have height. fix the child's height or just use ListView.
To sum up, the following worked for me.
sizes.bottomNavigationBar and sizes.appBar are the constant variables I made to customize the appbar and navbar.
RefreshIndicator(
onRefresh: widget.onRefresh,
child: SizedBox(
height: MediaQuery.of(context).size.height -
sizes.bottomNavigationBar -
sizes.appBar,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <Widget>[widget.bodyWidget]
)),
)
If you're finding any reliability, Refresh indicator does not show up part of the official doc would help.
If you're using ListView inside of Column with Expanded.
Try Removing
shrinkWrap:true

Flutter ListView.Builder() in scrollable Column with other widgets

I have a TabBarView() with an amount of different views. I want of them to be a Column with a TextField at top and a ListView.Builder() below, but both widgets should be in the same scrollable area (scrollview). The way I implemented it threw some errors:
#override
Widget build(BuildContext context) {
return new Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: new TextField(
decoration: new InputDecoration(
hintText: "Type in here!"
),
)
),
new ListView.builder(
itemCount: _posts.length, itemBuilder: _postBuilder)
],
);
}
Error:
I/flutter (23520): The following assertion was thrown during performResize():
I/flutter (23520): Vertical viewport was given unbounded height.
I/flutter (23520): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter (23520): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter (23520): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter (23520): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter (23520): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter (23520): instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
I/flutter (23520): the height of the viewport to the sum of the heights of its children.
I read about stacking the ListView.builder() in an Expanded-Area but it made the textfield kind of "sticky" which is not what I want. :-)
I also came across CustomScrollView but didn't fully understand how to implement it.
Here is the solution:
SingleChildScrollView(
physics: ScrollPhysics(),
child: Column(
children: <Widget>[
Text('Hey'),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount:18,
itemBuilder: (context,index){
return Text('Some text');
})
],
),
),
Placing the ListView inside an Expanded widget should solve your problem:
#override
Widget build(BuildContext context) {
return new Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: new TextField(
decoration: new InputDecoration(
hintText: "Type in here!"
),
)
),
new Expanded(child: ListView.builder(
itemCount: _posts.length, itemBuilder: _postBuilder))
],
);
}
Use SingleChildScrollView which allows the child widget to scroll
Solution
SingleChildScrollView(
child: Column(
children: <Widget>[
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
Two properties used here
shrinkWrap: true
only occupies the space it needs (it will still scroll when there more items).
physics: NeverScrollableScrollPhysics()
Scroll physics that does not allow the user to scroll. Means only Column+SingleChildScrollView Scrolling work.
Reason for the error:
Column expands to the maximum size in main axis direction (vertical axis), and so does the ListView
Solution
You need to constrain the height of the ListView, so that it does expand to match Column, there are several ways of solving this issue, I'm listing a few here:
If you want to allow ListView to take up all remaining space inside Column use Flexible.
Column(
children: <Widget>[
Flexible(
child: ListView(...),
)
],
)
If you want to limit your ListView to certain height, you can use SizedBox.
Column(
children: <Widget>[
SizedBox(
height: 200, // constrain height
child: ListView(),
)
],
)
If your ListView is small, you may try shrinkWrap property on it.
Column(
children: <Widget>[
ListView(
shrinkWrap: true, // use it
)
],
)
Use physics: NeverScrollableScrollPhysics() and shrinkWrap: true inside ListView.Builder() and enjoy
Here is an efficient solution:
class NestedListExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
const SliverToBoxAdapter(
child: Text('Header'),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(ctx, index) {
return ListTile(title:Text('Item $index'));
},
),
),
],
);
}
}
Here is a preview on dartpad.
You can use a SliverToBoxAdapter for the other children as only Slivers can be a direct child of a CustomScrollView.
If all the list items are the same height, then you could use SliverFixedExtentList, which is more efficient because the height of each child isn't calculated on the fly, but you will have to know the exact pixel height. You could also use a SliverPrototypeExtentList, where you provide the first item in the list(the prototype), and all the other children will use the height of the prototype so you don't need to know the exact height in pixels.
Use Expanded widget to constrain without overflowing those pixels, :)
Column(
children: <Widget>[
Expanded(
child: ListView(),
),
Expanded(
child: ListView(),
),
],
)
just add
Column(
mainAxisSize: MainAxisSize.max, //Add this line onyour column
children:[
SomeWidget(),
Expanded(child:ListView.builder())
]
)
In my case with a future i did it like this:
SingleChildScrollView(
physics: ScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Hey ho let's go!"),
Flexible(
child: FutureBuilder(
future: getData(),
builder: (BuildContext context,
AsyncSnapshot<List<Sale>> snapshot) {
if (snapshot.connectionState != ConnectionState.done ||
snapshot.hasData == null) {
return CircularProgressIndicator();
} else {
data = snapshot.data;
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return dataItemWidget(size, data[index], context);
},
itemCount: data.length,
);
}
},
),
),
],
),
),
//If you want Listview.builder inside ListView and want to scroll the parent ListView// //whenever the Items in ListView.builder ends or start you can do it like this
body: ListView(
physics: ScrollPhysics(),
children: [
SizedBox(height: 20),
Container( height: 110.0 *5, // *5 to give size to the container //according to items in the ListView.builder. Otherwise will give hasSize Error
child:ListView.builder(
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
itemCount: 5,
itemBuilder: (BuildContext context, int indexChild) {
return InkWell(child:Container(height:100));}))
),]),
The best way will be to make the column scrollable by making the column child of SingleChildScrollView and then assigning the same ScrollController to both the SingleChildScrollView and the ListView.builder. This will make the text field and the below ListView as scrollable.
Just add physics: NeverScrollableScrollPhysics() in ListView.builder() so you can scroll
Add physics: NeverScrollableScrollPhysics() inside Listview.builder() method and the nested Listview will scroll
Column is not scrollable, which is why the TextField on top wouldn't scroll but the ListView on the bottom would.
The best way to solve this in my opinion is to make your TextField the first item in your ListView.
So you won't need a column, your parent widget is the ListView, and its children are the TextField followed by the remaining items you build with _postBuilder.
return Column(
children: [
Text("Popular Category"),
Expanded(
child: ListView.builder(`enter code here`
shrinkWrap: false,
scrollDirection: Axis.horizontal,
itemCount: 3,
itemBuilder: (context, index) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("hello"),
],
);
}),
),
],
);
body: SingleChildScrollView(
physics: ScrollPhysics(),
child: Column(
children: [
getFiltersOnHomePage(),
SizedBox(
child: StreamBuilder(
stream: FirebaseFirestore.instance.collection('posts').snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: snapshot.data!.docs.length,
itemBuilder: (ctx, index) => Container(
margin: EdgeInsets.symmetric(
horizontal: width > webScreenSize ? width * 0.3 : 0,
vertical: width > webScreenSize ? 15 : 0,
),
child: PostCard(
snap: snapshot.data!.docs[index].data(),
),
));
},
),
),
],
),
),[enter image description here][1]***You will be able to scroll through the page by using Expanded Widget
Blockquote
Using this you can scroll over the entire page. This page includes a row and a listview builder inside a scrollable column.
In my case, I added a Container with transparent color and height up to 270 to solve this one.
Column(
children: <Widget>[
ListView(
shrinkWrap: true, // use it
),
Container(
color: Colors.transparent,
height: 270.0,
),
],
)