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.
Related
Before using ListView.seperator, the for loop didn't cause any problems:
body: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(60),
children: [
for (var question in questionsList)
Text(
question,
textScaleFactor: 1.5,
),
]
)
Now that it's been switched, I keep getting an error.
body: Center(
child:
ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(60),
itemBuilder: (BuildContext context, int index) {
return Container(
for (var question in questionsList)
alignment: const Alignment(0, .7),
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
question,
textScaleFactor: 1.5,
),
),
);
},
separatorBuilder: (BuildContext context, int index) => const
Divider(color: Colors.black,)
,
itemCount: questionsList.length
),
),
I've tried moving it to the top of ListView.seperator - didn't like that, at the top of Container() - didn't like that, within Padding() - it didn't like that either.
I want to create a horizontal list, with each question in it's own little box at the bottom of the screen. I want the questions to be looped through from a separate file.
The for loop you were using before is called "collection for". It's used within a collection literal (that is, within [] for a List literal, {} for Set and Map literals) to generate elements for the collection. For more information, I strongly recommend reading Bob Nystrom's article that introduces them.
ListView.separated doesn't take a List of items; it instead uses an itemBuilder callback that is expected to return each individual item. You don't need to use a loop with it because ListView.separated is already iterating for you; you just need your callback to return the appropriate element that corresponds to index.
This is untested, but you probably want something like:
body: Center(
child:
ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(60),
itemBuilder: (BuildContext context, int index) {
return Container(
alignment: const Alignment(0, .7),
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
questionsList[index], // ***
textScaleFactor: 1.5,
),
),
);
},
separatorBuilder: (BuildContext context, int index) => const
Divider(color: Colors.black) ,
itemCount: questionsList.length,
),
),
#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
I am building a service app where features will be displayed as gridview in flutter, when tappeed/pressed on the specific tile on the grid, it should be navigated to another android page. Is there any way to do this using flutter?
I tried using gridview, but unable to access independent tiles on the grid. when i tried using container and tried to used buttons then there will be pixel overflow. Is there any way to fix this?
body: CustomScrollView(
primary: false,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.all(20),
sliver: SliverGrid.count(
crossAxisSpacing: 10,
mainAxisSpacing: 10,
crossAxisCount: 2,
children: <Widget>[
Container(
padding: const EdgeInsets.all(8),
child: Image.asset('assets/images/lungs.jpg'),
),
Container(
padding: const EdgeInsets.all(8),
child: Image.asset('assets/images/cancer.jpg'),
),
],
),
),
],
)
this is another method i tried using
You can access index on each tapped InkWell, see below code, here a list of 100 items is generated, and each index is printed on onTap: (){print('$index');},
SliverPadding(
padding: const EdgeInsets.all(20),
sliver: SliverGrid.count(
// Create a grid with 2 columns. If you change the scrollDirection to
// horizontal, this produces 2 rows.
crossAxisSpacing: 10,
mainAxisSpacing: 10,
crossAxisCount: 2,
// Generate 100 widgets that display their index in the List.
children: List.generate(100, (index) {
return Center(
child: InkWell(
onTap: (){
print('$index');
// use this line if you want to pass index position on the next page
Navigator.of(context).pushNamed(
'YOUR_PAGE', arguments: '$index');
//or if you don't want to pass anything use this
Navigator.of(context).pushNamed(
'YOUR_PAGE');
},
child:Text(
'Item $index',
style: Theme.of(context).textTheme.headline,
)
),
);
}),
),
),
How to fetch passed argument on next page?
#override
Widget build(BuildContext context) {
// Extract the arguments from the current ModalRoute settings
final var args = ModalRoute.of(context).settings.arguments;
To know more about Pass arguments to a named route Read this
Output
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
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( ...
],
),
);
}
}