I am trying to have a ListView with one single scroll. I want to be able to scroll down and see every widget. Currently, I have multiple widgets before a GridView. The problem with this is that the GridView has its own scroll. A good example of what I want is similar to the Instagram profile page. When you scroll down in the Instagram profile page, every element scrolls down. There are no nested scrolls.
This is my current code:
body: ListView(
children: <Widget>[
...
...
Container(
child: GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
children: List.generate(100, (index) {
return Container(
alignment: Alignment.center,
margin: EdgeInsets.all(3.0),
height: MediaQuery.of(context).size.height,
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image:'https://picsum.photos/${300}/${300}/',
),
);
}),
),
)
]
)
There's Nested Scrolled Widgets, the CustomScrollView which takes Slivers to achieve what you're looking for. SliverGrid and SliverList are the ones you need to achieve your goal:
Widget sliverScroll() {
return CustomScrollView(
slivers: <Widget>[
SliverGrid(
delegate: SliverChildBuilderDelegate((context, index) {
return Container();
}, childCount: 9),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return Container();
}, childCount: 6),
),
],
);
}
You can customize as you wish, changing the order or it, item count, etc
Related
I have already gone through this post
for nested scrolling but it is not the correct way according to me as explained in this video from the official flutter channel
I want to achieve the below layout
The list header like Claim requested credentials,Claim received credentials,Pending Requests etc are dynamic and will be coming from backend. Also each item in those list header like Module 1: Designing Financial Services are also dynamic
So I need list within a list
I am using a CustomScroll but I am not able to achieve the inner list view
I am looking for a lazy list option and not just mapping the inner list over a column or a list as the inner list might contain 100 items
Here is what I have achieved
Here is the sample code
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
HeaderGradient(),
Positioned(
top: 110,
left: 0,
right: 0,
bottom: 0,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: const BoxDecoration(
color: grayColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: CustomScrollView(
slivers: [
const SliverToBoxAdapter(
child: ManageCredentialHeader(),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return ManageCredentialCard();
}, childCount: 10))
],
),
),
)
],
),
);
}
}
and
class ManageCredentialCard extends StatelessWidget {
const ManageCredentialCard({super.key});
#override
Widget build(BuildContext context) {
return Card(
color: Theme.of(context).colorScheme.background,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Column(
children: [
const ManageCredentialCardHeader(),
const ManageCredentialCardItem()
],
),
),
);
}
}
ManageCredentialCardItem is the inner list
As soon as I wrap ManageCredentialCardItem inside a ListView.builder I get error saying
RenderFlex children have non-zero flex but incoming height constraints are
unbounded.
When a column is in a parent that does not provide a finite height constraint,
for example if it is
in a vertical scrollable, it will try to shrink-wrap its children along the
vertical axis. Setting a
flex on a child (e.g. using Expanded) indicates that the child is to expand to
fill the remaining
space in the vertical direction.
Check the sample repo to check what I have tried and full source code
I was referring generating everything on sliver like
child: CustomScrollView(
slivers: [
const SliverToBoxAdapter(
child: ManageCredentialHeader(),
),
for (int i = 0; i < 10; i++)
SliverPadding(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
childCount: 10 + 1, //extend by 1 for header
(context, index) {
return index == 0
? const ManageCredentialCardHeader()
: const ManageCredentialCardItem();
}),
),
)
],
),
Also you can create custom-sliver for children.
To solve the ‘ListView.builder’ problem, I suggest to wrap it into an ’Expanded’ and set the ’shrinkWrap’ property of the ’ListView’ to true.
As for the list inside a list problem I’m not sure I understand correctly, but setting the ’primary’ property of the inner List to false should make it so it follows the scroll of the general list and avoids scrolling internally.
It might be needed to also set the ’shrinkWrap’ to true since it has no defined height, making it not lazy, but I’d try also wrapping it with ’Expanded’ as it might have the same result keeping it lazy
As #Yeasin Sheikh suggested, you dont need to use Listview.builder inside ManageCredentialCardItem because the parent of ManageCredentialCardItem is handling the scroll i.e
SliverList( // 👈 This will handle scroll
delegate: SliverChildBuilderDelegate((context, index) {
return ManageCredentialCard(); //👈 So you can use column instead
},childCount: 10))
If you have useCase where you have to use ListView.builder for fetching the results from remote source, you can use shrinkWrap: true prop inside ListView.builder
I'm not really sure about what effect you want to achieve, but :
If your goal is to display the full list of items in your ManageCredentialCard like this:
you just need to update the ManageCredentialCard widget with the following
class ManageCredentialCard extends StatelessWidget {
const ManageCredentialCard({super.key});
#override
Widget build(BuildContext context) {
return Card(
color: Theme.of(context).colorScheme.background,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Column(
children: [
const ManageCredentialCardHeader(),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return const ManageCredentialCardItem();
},
itemCount: 20,
)
// const ManageCredentialCardItem()
],
),
),
);
}
}
BUT
If your goal is to limit the size of your Cards, and be able to scroll the Items list like this
you will have to define a height limit to your Cards with a SizeBox.
class ManageCredentialCard extends StatelessWidget {
const ManageCredentialCard({super.key});
#override
Widget build(BuildContext context) {
return Card(
color: Theme.of(context).colorScheme.background,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Column(
children: [
const ManageCredentialCardHeader(),
SizedBox(
height: 150,
child: ListView.builder(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) {
return const ManageCredentialCardItem();
},
itemCount: 10,
),
)
// const ManageCredentialCardItem()
],
),
),
);
}
}
The only thing you'll have to do then would be to scroll the main Listview when reaching the end of one of the inner-lists.
To do so, you can use the NotificationListener widget as explained in this post :
Flutter : ListView : Scroll parent ListView when child ListView reach bottom - ClampingScrollPhysics not working in sized container
Use these below codes in ListView, SingleChildScrollView, ListView.builder() or any scroll view widgets.
shrinkWrap: true,
physics: NeverScrollablePhysics(),
For more, please go through this article.
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
_firstListView(),
_secondListView(),
],
),
);
}
Main class that calls other listView builders
Widget _firstListView() {
return Container(
color: Colors.green,
child: ListView.builder(
itemCount: 200,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) => RecCell(
index: index,
),
),
);
}
first listView builder
Widget _secondListView() {
return Container(
color: Colors.red,
child: ListView.builder(
itemCount: 200,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) => ListTile(title: Text("Second $index")),
),
);
}
the second listView builder
RecCell is another statefull widget that prints in init method when the widget is build and it build 200 times on restart and never recreated on scrolling
I want them to reuse each time parent listview scrolls any help will appreciated..
Well, wrapping them in a ListView removes the lazy loading benefit of ListView.builder, because the outer ListView wants to load all children at once. I think the best way is the combine the data of the two ListView.builders into a single one. Although, wrapping them in a colored container would not be possible then, but maybe you get the desired result by wrapping the items in it. So something like:
Widget builds(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 400,
shrinkWrap: true,
itemBuilder: (context, index) => index < 200
? Container(
color: Colors.green,
child: RecCell(
index: index,
),
)
: Container(
color: Colors.red,
child: ListTile(title: Text("Second ${index - 200}")))));
}
You could use NestedScrollView if you have nested listview in the body of the parent widget.
try this
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
Container(height: 300, child: _firstListView()),
Container(height: 300, child: _secondListView()),
],
),
);
}
-----------OR------------
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
children: <Widget>[
Container(height: 300, child: _firstListView()),
Container(height: 300, child: _secondListView()),
],
),
);
}
Use CustomScrollView instead ListViews for this case
CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Container(),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Container(),
),
),
],
);
because the ListView requires all available space and expands its own size as much as possible to understand how many child elements can be displayed
I did this and worked for me
Map<String, dynamic> d = {
0:..,listView widget,
1:[...list of widgets]
}
I am making a map containing all my widgets, and in the build method I am returning a listview with those widgets
return ListView(
children:[
d[0],
...d[1] // this will extract each widget from the list and insert it to list view
]
)
In this way my d[0] is horizontal listview builder and d1 are other widget that I am adding into parent list view directly now they all are rerendering when visible. Had to do some changing in my previous code
Final output:
output
In my Flutter app, I have a ListView inside a ListView. Please check below
ListView(
children: < Widget > [
//Stack Widget
//More Widgets
Container(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(), //Disable Scrolling
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return Container(
margin: EdgeInsets.only(bottom: 10, left: 10, right: 10),
child: _createAdvertisement(),
);
},
),
),
],
);
There are 100 items in the second ListView and I used physics: NeverScrollableScrollPhysics() to stop it from scrolling. I do not want this to scroll because the top ListView can scroll the entire page, so I want the content in the second ListView to be added to the bottom of the page. User can scroll the entire page up, to see the content below.
However my trick of using physics: NeverScrollableScrollPhysics() didnt work, instead it limited the number of elements the ListView is going to generate. As a result, now I have less than 10 items in that listview, instead of 100.
How can I fix this issue?
You can add "shrinkWrap: true," and remove "height: MediaQuery.of(context).size.height,".
ListView(
children: <Widget>[
Text("1"),
Text("2"),
Container(
child: ListView.builder(
shrinkWrap: true, // add this line.
physics: NeverScrollableScrollPhysics(), //Disable Scrolling
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return Container(
margin: EdgeInsets.only(bottom: 10, left: 10, right: 10),
child: Text("test"),
);
},
),
),
Text("3"),
],
),
I'm developing a new Flutter app on Android Studio. One page of my app gets the information from a Future. With the future's result I'm creating a ListView.builder():
Here's the code:
Widget encabezados(BuildContext context, AsyncSnapshot snapshot)
{
ListView(
children: <Widget>[
new Card(
elevation: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(width: 0.0,),
Category(
backgroundColor: Colors.deepOrange,
val: clientesacobrar.toString(),
title: "Clientes a cobrar",
),
SizedBox(width: 10.0,),
Category(
backgroundColor: Colors.blue,
title: "Clientes cobrados",
val: clientescobrados.toString(),
),
SizedBox(width: 10.0,),
Category(
val: formatter.format(montoacobrar),
//val: records.length.toString(),
backgroundColor: Colors.green,
title: "Monto a cobrar",
)
],
),
),
new ListView(
shrinkWrap: true,
scrollDirection: Axis.vertical,
//elevation: 3,
children: <Widget>[
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
//ShrinkWrappingViewport: true,
itemCount: records.length,
itemBuilder: (BuildContext context, int index) {
return records.isNotEmpty
? Card(
child: Padding (
padding: const EdgeInsets.all(16.00),
child: Text(records[index].nombre),
)
)
: CircularProgressIndicator();
}
)
],
)
],
);
}
As you can see in the listviews widgets I'm using shrinkWrap set to true, because if I don't set this I get:
I/flutter (22634): The following assertion was thrown during performResize():
I/flutter (22634): Vertical viewport was given unbounded height.
I/flutter (22634): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter (22634): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter (22634): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter (22634): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter (22634): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter (22634): instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
I/flutter (22634): the height of the viewport to the sum of the heights of its children.
But if I set the shrinkwrap property I'm not able to scroll the page on my app
I have tried putting the ListView.builder() into:
container widget , column widget, ListView widget but in the ListView.builder() I'm always asked to set the shrinkwrap property.
You need to constrain your parent widget so it knows where the bounds are.
For that scenario, wrapping in a Container alone, won’t have any effect unless you explicit constrain it (eg. by setting constrains or just giving it a explicit height).
Container(
height: 200.0, // or whatever
child: ListView(
....
)
)
As an alternative, you can also set your inner ListView physics to NeverScrollableScrollPhysics() which will prevent any scroll from happening in the inner one and only the top scroll view will control scroll.
If all of that doesn't help, you may need to use a CustomScrollView which lets you nest multiple scroll views, basically a Sliver.
Try to add physics: NeverScrollableScrollPhysics() to inner ListView.
Miguel Ruivo's answer is correct but rather than using a Container with an explicit height, you can instead wrap the ListView with Expanded to give it the remaining height and allow it to scroll if needed.
Note: Make sure the Expanded widget is a descendant of Row, Column, or Flex.
Expanded(
child: ListView(
...
)
)
I had the same issue and found out that the main thing is using the physics: NeverScrollableScrollPhysics property inside of your ListView.Builder() like so:
body: Column(
children: [
_buildHeader(),
Container(
// height: 40,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(), // <= This was the key for me
shrinkWrap: true,
itemCount: 1,
itemBuilder: (_, i) {
return _horizontalListView();
},
),
),
],
),
I tested and It works with Column, Container, Expanded widgets as a parent widget.
So something that seems odd is the fact that you have a whole heap of nested ListView's. I suggest try limiting it to just one.
It's a little difficult to see what the intended outcome should be... Is the "Category card" meant to be static with the ListView.builder part scrolling underneath? Or does the "Category card" scroll with the content?
Either way, I think I've got something that you should be able to use.
Included are ways of accomplishing either the static or scrolling head.
A note on the static header solution: I'm using a Column instead of a ListView This ensures we don't have top level scrolling going on. First child is your "header" with the second child the ListView responsible for the scrolling. This ListView is wrapped in Expanded to make sure the scrolling ListView takes up all of the remaining screen height.
Hope this helps
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SO Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample Code'),
),
body: _buildViewWithStaticCategories());
// OR
// body: _buildViewWithScrollingCategories());
}
Widget _buildViewWithStaticCategories() {
return Column(
children: <Widget>[
_buildCategoriesCard(),
Expanded(
child: ListView.builder(
itemCount: 20, // records.length
itemBuilder: (BuildContext context, int index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.00),
child: Text(index.toString()),
),
);
}),
),
],
);
}
Widget _buildViewWithScrollingCategories() {
return ListView.builder(
itemCount: 21, // records.length + 1 for the Categories card
itemBuilder: (BuildContext context, int index) {
return index == 0
? _buildCategoriesCard()
: Card(
child: Padding(
padding: const EdgeInsets.all(16.00),
child: Text(index.toString()),
),
);
},
);
}
Widget _buildCategoriesCard() {
return Card(
elevation: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('one'), // Category( ...
SizedBox(width: 10.0),
Text('two'), // Category( ...
SizedBox(width: 10.0),
Text('three'), // Category( ...
],
),
);
}
}
I am using ListView.builder function to create a list of items. However, the space between each item in iOS is huge (screenshot). Do you know how to remove item? It seems it is by default, because I do not adding it.
code:
ListView:
return Scaffold(
body: ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
final model = data[index];
if (model.path.isEmpty)
return Divider(color: Colors.grey[500], indent: 40.0);
else
return ItemMenu(model.path, model.name);
}),
);
Item:
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Image.asset(path, width: 100, height: 100,color: Colors.grey[500]),
Text(name, style: MyTextTheme().getLightSmallGrey().copyWith(fontSize: 20.0, fontWeight: FontWeight.w700))
],
);
If you are using a ListTile widgets as your ListView items, you can add the dense = true option:
ListView(
children: [
ListTile(
dense: true,
...
),
ListTile(
dense: true,
...
),
]
),
Most of the answers recommended using dense: true but that just made the text smaller in my case and did not actually reduce the spacing between List Items.
Solution:
Use visualDensity property instead within the ListTile.
Code Example:
visualDensity:VisualDensity(horizontal: 0, vertical: -4),
i just resolved that issue but within an image network with "fit" property: Boxfit.cover/fill