What is the preferred way to achieve a nested ListView, or in other words, ListView Widgets that could be included within a scrollable parent?
Imagine a "Reports" page, where a section is an itemized list.
For child ListView, use that parameter:
shrinkWrap: true,
physics: ClampingScrollPhysics(),
Adding physics: ClampingScrollPhysics() and shrinkWrap: true did the trick for me.
sample code:
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: 123,
itemBuilder: (BuildContext context, int index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Parent'),
ListView.builder(
itemCount: 2,
physics: ClampingScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return Text('Child');
}),
],
);
}),
)
],
),
);
}
If you want to have the inner ListView be scrollable independently of the main scroll view, you should use NestedScrollView. Otherwise, use a CustomScrollView.
Here is some code illustrating the NestedScrollView approach.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
new SliverAppBar(
pinned: true,
title: new Text('Flutter Demo'),
),
];
},
body: new Column(
children: <Widget>[
new FlutterLogo(size: 100.0, colors: Colors.purple),
new Container(
height: 300.0,
child: new ListView.builder(
itemCount: 60,
itemBuilder: (BuildContext context, int index) {
return new Text('Item $index');
},
),
),
new FlutterLogo(size: 100.0, colors: Colors.orange),
],
),
),
);
}
}
For inner Listview I have just added below code and it solved for me
shrinkWrap: true,
physics: ScrollPhysics(),
Screenshot:
Code:
var _container = Container(
height: 200,
color: Colors.blue,
margin: EdgeInsets.symmetric(vertical: 10),
);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("ListView")),
body: Padding(
padding: const EdgeInsets.all(40.0),
child: ListView( // parent ListView
children: <Widget>[
_container,
_container,
Container(
height: 200, // give it a fixed height constraint
color: Colors.teal,
// child ListView
child: ListView.builder(itemBuilder: (_, i) => ListTile(title: Text("Item ${i}"))),
),
_container,
_container,
_container,
],
),
),
);
}
Thanks to Serdar Polat:
ListView.builder( // outer ListView
itemCount: 4,
itemBuilder: (_, index) {
return Column(
children: [
Container(
color: Colors.blue,
alignment: Alignment.center,
child: Text('Header $index'),
),
ListView.builder( // inner ListView
shrinkWrap: true, // 1st add
physics: ClampingScrollPhysics(), // 2nd add
itemCount: 10,
itemBuilder: (_, index) => ListTile(title: Text('Item $index')),
)
],
);
},
)
shrinkWrap to wrap your content and ClampingScrollPhysics to use the parent scroll
ListView.builder(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemCount: yourList.length,
itemBuilder: (context, index) => YourWidget(items[index]),
),
I use this:
scrollController.addListener(onScroll);
void onScroll(){
if(scrollController.offset == 0.0
|| scrollController.position.extentBefore == 0.0
|| scrollController.position.extentAfter == 0.0){
scrollPhysics = NeverScrollableScrollPhysics();
Future.delayed(Duration(seconds: 1), (){
scrollPhysics = ClampingScrollPhysics();
setState((){});
});
setState((){});;
}
}
Expanded(
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8),
itemCount: requestList.length,
itemBuilder: (BuildContext context, int index) {
int que = index;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: const EdgeInsets.only(
left: 20,
top: 10,
bottom: 10,
right: 20),
child: Text(
'${que++} . ${requestList[index].question}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: HexColor(HexColor.black),
fontFamily: 'montserrat_regular',
decoration: TextDecoration.none,
),
)),
ListView.builder(
itemCount: requestList[index].questionOptions!.length,
physics: ClampingScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int subindex) {
return Row(
children: <Widget>[
Radio(
value: 1,
groupValue: radio_value[index],
onChanged: (values) async {
setState(() {
radio_value[index] = 1;
qutionCheckModel[index].response =
"yes";
});
}),
Container(
child: Text(
requestList[index].questionOptions![subindex],
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: HexColor(HexColor.black),
fontFamily: 'montserrat_regular',
decoration: TextDecoration.none,
),
),
),
],
);
}),
],
);
}),
),
Related
In my application I am listing in an appBar several Containers that have the names of product categories, these categories are being listed in the body with their respective products.
The ListView that is in the appBar has the same indexes of the ListView of the body, so the idea was to press the index 'x' in the appBar and the user would be redirected to the index 'x' in the body.
I tried many solutions, one of then was the package https://pub.dev/packages/scrollable_positioned_list, but it did not works because when calling the function to scroll my list just disappears.
Here's de code:
return Scaffold(
backgroundColor: Colors.white,
appBar: PreferredSize(
preferredSize: Size.fromHeight(120),
child: Column(
children: [
AppBar(...),
Expanded(
child: Container(
color: AppColors.primary,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: widget.listaProdutos.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.symmetric(...),
child: GestureDetector(
child: Container(
decoration: BoxDecoration(...),
child: Padding(...),
child: Center(
child: Text(
widget.listaProdutos[index].dsGrupo,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
),
onTap: () {
SHOULD SCROLL TO INDEX
},
),
);
},
)
),
),
],
),
),
body: SingleChildScrollView(
child: Column(
children: [
Container(
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.listaProdutos.length,
itemBuilder: (context, indexGrupo) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(...),
ListView.builder(..),
],
);
},
),
),
],
),
),
);
You can use PageView with scrollDirection: Axis.vertical,
class TFW extends StatefulWidget {
const TFW({super.key});
#override
State<TFW> createState() => _TFWState();
}
class _TFWState extends State<TFW> {
final PageController controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: PreferredSize(
preferredSize: Size.fromHeight(100),
child: Expanded(
child: ListView.builder(
itemCount: 100,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
controller.animateToPage(index,
duration: Duration(milliseconds: 400),
curve: Curves.easeIn);
},
child: SizedBox(width: 100, child: Text("$index")),
);
},
),
),
)),
body: PageView.builder(
controller: controller,
itemCount: 100,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return Container(
color: index.isEven ? Colors.red : Colors.blue,
child: Text("$index"),
);
},
),
);
}
}
Fortunately I was able to resolve the problem. To help members who may have the same doubt I will register here the solution that worked for me. (sorry for the bad English)
Question: Why ScrollablePositionedList wasn't working? (as I mentioned iniatily)
Response: I was using the ScrollablePositionedList within a SingleChildScrollView, and for some reason when using the scrollTo or jumpTo function, the information that was visible simply disappeared. For that reason, I was trying to find a way to get success using a ListView (what came to nothing).
Solution: ... Trying to figure out why the ScrollablePositionedList wasn't working as it should ...
The initial structure was:
body: SingleChildScrollView(
child: Column(
children: [
Container(
child: ScrollablePositionedList.builder(
Changed for:
body: ScrollablePositionedList.builder(
The only reason for all this confusion is that ScrollablePositionedList's indexing functions for some reason don't work as they should if it's inside a SingleChildScrollView. So, take off SingleChildScrollView and all good.
My aim is to put a Divider line after the text of the inner ListView.builder.
What's the way to do that?
List<String> list1 = ["pppp", "qqqq", "rrrr"];
List<String> list2 = ["aaaa", "bbbb", "cccc"];
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
body: new Column(
children: <Widget>[
new Expanded(
child: new ListView.builder(
itemCount: list1.length,
itemBuilder: (BuildContext ctxt, int Index) {
return new ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: list2.length,
itemBuilder: (BuildContext ctxt, int Index) {
return new Text(
list2[Index],
style: TextStyle(color: Colors.green, fontSize: 25),
);
}
);
}),
),
],
),
);
}
You can add a divider by placing SizedBox as shown in the code below:
List<String> list1 = ["pppp", "qqqq", "rrrr"];
List<String> list2 = ["aaaa", "bbbb", "cccc"];
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
body: new Column(
children: <Widget>[
new Expanded(
child: Column(
children: [
new ListView.builder(
itemCount: list1.length,
itemBuilder: (BuildContext ctxt, int Index) {
return new ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: list2.length,
itemBuilder: (BuildContext ctxt, int Index) {
return Text(
list2[Index],
style: TextStyle(color: Colors.green, fontSize: 25),
);
});
}),
Divider(
height: 3,
thickness: 1,
indent: 10, // Space at the start.
endIndent: 10, // Space at the end.
),
],
),
),
],
),
);
}
Please change the height of the SizedBox as required.
Let's try with ListView.separated
List<String> list1 = ["pppp", "qqqq", "rrrr"];
List<String> list2 = ["aaaa", "bbbb", "cccc"];
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
body: new Column(
children: <Widget>[
new Expanded(
child: new ListView.separated(
itemCount: list1.length,
itemBuilder: (BuildContext ctxt, int Index) {
return new ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: list2.length,
itemBuilder: (BuildContext ctxt, int Index) {
return new Text(
list2[Index],
style: TextStyle(color: Colors.green, fontSize: 25),
);
},
);
},
separatorBuilder: (context, build)=>Divider(
thickness: 1,
color: Color(0xff002540).withOpacity(.1),
),
),
),
],
),
);
}
Output:
You can use ListView.separated
More on that https://medium.com/flutter-community/flutter-adding-separator-in-listview-c501fe568c76#:~:text=separated.-,ListView.,indices%20%E2%89%A5%200%20and%3C%20itemCount%20.
I am having a hard time figuring out how to get my FilterChips in a row.
I would like to display all FilterChips per category in a row and not in a seperated column.
Tried different approaches but non of them seems to work.
Hopefully someone can help me with this, thanks for help!
Here is my code:
Widget _buildListView(List<Category> categories) {
return Column(
children: [
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: categories.length,
itemBuilder: (context, index) {
return _buildSection(categories[index]);
},
),
),
],
);
}
Widget _buildSection(Category category) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
category.title,
style: TextStyle(fontSize: 18),
),
// How can I get my FilterChips side by side?
Row(
children: [
Flexible(
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: category.interests.length,
itemBuilder: (context, index) {
Interest interest = category.interests[index];
return FilterChip(label: Text(interest.interest), onSelected: (isSelected){
selectedInterests.add(interest.id);
});
return Text(interest.interest);
}),
),
],
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: categories,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Category> categories = snapshot.data;
return _buildListView(categories);
}
return Center(child: CircularProgressIndicator());
},
),
);
}
class StackOver extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.only(
top: 60,
left: 10.0,
right: 10.0,
),
child: Wrap(
children: List.generate(
10,
(index) {
return FilterChip(
label: Text('index $index'),
onSelected: (val) {},
);
},
),
),
),
);
}
}
RESULT:
I am trying to create a custom drawer.
I want to make DrawerHeader fixed. However, I want ExpansionTile and ListView scrollable.
https://imgur.com/a/lWeDWOO ,
https://imgur.com/a/tR68ECK
class _NavigationDrawerState extends State<NavigationDrawer> {
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: <Widget>[
DrawerHeader(
child: Column(....),
ExpansionTile(
title: Text('Roles'),
children: [
ListView.builder(
itemBuilder: _buildRolesList,
itemCount: roles.length,
shrinkWrap: true,
)
],
),
ListView.builder(
shrinkWrap: true,
itemBuilder: _buildList,
itemCount: myList.length,
),
],
),
);
}
Widget _buildList(BuildContext context, int index) {
return ListTile(
leading: Icon(icons[myList[index].toLowerCase()]),
title: Text(myList[index]),
);
}
Widget _buildRolesList(BuildContext context, int index) {
return ListTile(
dense: true,
title: Text(roles[index]),
onTap: () {},
);
}
}
Try this:
class _NavigationDrawerState extends State<NavigationDrawer> {
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: <Widget>[
ExpansionTile(
title: Text('Roles'),
children: [
SizedBox(
height: MediaQuery.of(context).size.height - 100,
child: ListView.builder(
itemBuilder: _buildRolesList,
itemCount: roles.length,
shrinkWrap: true,
),
)
],
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemBuilder: _buildList,
itemCount: myList.length,
),
),
],
),
);
}
Widget _buildList(BuildContext context, int index) {
return ListTile(
leading: Icon(icons[myList[index].toLowerCase()]),
title: Text(myList[index]),
);
}
Widget _buildRolesList(BuildContext context, int index) {
return ListTile(
dense: true,
title: Text(roles[index]),
onTap: () {},
);
}
item1, item2, item3 are all lists and i am trying to build a list view with all the items that each list holds, where all this three listview builders would take as much place as they need, lets say that item1 has 20 items in it and it will take 20 rows, and item2 has 25 etc. When i try to use a row and listview.builder it gives me an error.
What I am trying to do:
body: Container(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(widget.item1[index]),
);
},
itemCount: widget.item1 == null ? 0 : widget.item1.length,
),
],
),
],
),
),
Among a huge list of crash report:
flutter: Another exception was thrown: NoSuchMethodError: The method '<=' was called on null.
flutter: Another exception was thrown: NoSuchMethodError: The getter 'visible' was called on null.
The problem is that the only way I know is to make it with an Expanded, and it will divide the screen in three and make equal space or i can manipulate with flex, but this is not what i want.
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(widget.item1[index]),
);
},
itemCount: widget.item1 == null ? 0 : widget.item1.length,
),
),
Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(widget.item2[index]),
);
},
itemCount: widget.item2 == null ? 0 : widget.item2.length,
),
),
Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(widget.item3[index]),
);
},
itemCount: widget.item3 == null ? 0 : widget.item3.length,
),
),
],
),
),
This is what I was talking about, a List using SingleChildScrollView and Column, you can also do the same with Slivers
A sample I made for you:
final List<String> item1 = List.generate(5, (val) => "item1 $val");
final List<String> item2 = List.generate(5, (val) => "item2 $val");
final List<String> item3 = List.generate(5, (val) => "item3 $val");
#override
Widget build(BuildContext context) {
final items = <Widget>[];
for (String i1 in item1) {
items.add(ListTile(
title: Text(i1),
));
}
for (String i2 in item2) {
items.add(ListTile(
title: Text(
i2,
style: TextStyle(
color: Colors.red,
),
),
));
}
for (String i3 in item3) {
items.add(ListTile(
title: Text(
i3,
style: TextStyle(
color: Colors.blue,
),
),
));
}
return Scaffold(
body: Container(
child: SingleChildScrollView(
child: Column(
children: items,
),
),
),
);
}
}
Another way using ListView.builder :
return Scaffold(
body: Container(
child: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(item1[index]),
);
},
itemCount: item1.length,
),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
item2[index],
style: TextStyle(
color: Colors.red,
),
),
);
},
itemCount: item2.length,
),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(item3[index]),
);
},
itemCount: item3.length,
),
],
),
),
),
);
}
Don't forget to check this awesome article about Slivers by Emily Fortuna (Flutter team)
https://medium.com/flutter/slivers-demystified-6ff68ab0296f