I am making an application where I can run through items in a GridView on a profile page, like on Instagram when we scroll our posts.
I want to load more items (15 per 15) when I scroll on my GridView. I want an infinite loading.
So I added a ScrollListener to my GridView.
If I put an "initialScrollOffset" to "5.0" in attribute to my ScrollListener, it will load the 15 first items and make one loading, so it's add 15 items (work only 1 time), but if I let the default value, it loads no items.
I would like to have an infinite loading.
My GridView code :
import 'dart:developer';
import 'package:dresskip/model/item_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ItemSection extends StatefulWidget {
const ItemSection({Key? key}) : super(key: key);
@override
_ItemSectionState createState() => _ItemSectionState();
}
class _ItemSectionState extends State<ItemSection> {
List<Item> items = [];
bool isLoading = false;
int pageCount = 1;
ScrollController _scrollController = ScrollController(initialScrollOffset: 5.0);
@override
void initState() {
super.initState();
///LOADING FIRST DATA
addItemsToList(1);
_scrollController.addListener(_scrollListener);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(),
GridView.count(
controller: _scrollController,
crossAxisCount: 3,
shrinkWrap: true,
//physics: const ScrollPhysics() /*AlwaysScrollableScrollPhysics()*/,
mainAxisSpacing: 0,
children: items.map((value) {
return Image.network(value.picture);
}).toList(),
)
],
);
}
//// ADDING THE SCROLL LISTINER
_scrollListener() {
//inspect(_scrollController.offset);
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent &&
!_scrollController.position.outOfRange) {
setState(() {
print("comes to bottom $isLoading");
isLoading = true;
if (isLoading) {
print("RUNNING LOAD MORE");
pageCount = pageCount 1;
addItemsToList(pageCount);
}
});
}
}
addItemsToList(int page) {
//if (page < 5) {}
Item myItem = Item(
name: "test",
brand: "test",
color: ["0xFF39BDC8", "0xFFdb8abc", ""],
picture:
"https://images.pexels.com/photos/9676177/pexels-photo-9676177.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
//"https://scontent.fcdg2-1.fna.fbcdn.net/v/t1.6435-9/171944671_3950113148381954_7059062044076097927_n.jpg?_nc_cat=110&ccb=1-5&_nc_sid=09cbfe&_nc_ohc=gxbPXmRQmN8AX9V5Bx5&_nc_ht=scontent.fcdg2-1.fna&oh=ac2a57c8c1d1b0b01fcf131ac42c4023&oe=6190A9BF",
solo: false,
clean: true,
type: "test");
for (int i = (pageCount * 15) - 15; i < pageCount * 15; i ) {
items.add(myItem);
isLoading = false;
}
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
Item model class
class Item {
String name;
String brand;
List<String> color;
String picture;
bool solo;
bool clean;
String type;
Item({
required this.name,
required this.brand,
required this.color,
required this.picture,
required this.solo,
required this.clean,
required this.type,
});
}
The first part (profile section) code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '/assets/constants.dart' as constants;
import '../../assets/dresskip_icon_icons.dart' as DresskipIcons;
class ProfileSection extends StatelessWidget {
final List<String> description;
final VoidCallback onClicked;
const ProfileSection({
Key? key,
required this.description,
required this.onClicked,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment
.spaceBetween, //pour que chaque colonnes dans la ligne ait leurs propres tailles
crossAxisAlignment: CrossAxisAlignment
.start, //pour tout coller en haut du container
children: <Widget>[
const Icon(Icons.local_laundry_service),
Stack(children: [
buildImage(),
Positioned(
bottom: 0,
right: 4,
child:
buildEditIcon(Color(constants.COLOR_BLUE_DRESSKIP)))
]),
const Icon(Icons.settings),
],
),
// Partie description
Container(
child: Text(description[0]),
margin: EdgeInsets.fromLTRB(50, 5, 50, 5)),
// Partie Instagram
Container(
child: Row(
children: <Widget>[
Container(
child: const Icon(DresskipIcons.DresskipIcon.instagram),
margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
),
Expanded(child: Text(description[1]))
],
),
margin: const EdgeInsets.fromLTRB(20, 5, 20, 5)),
// Partie Facebook
Container(
child: Row(
children: <Widget>[
Container(
child: const Icon(Icons.facebook),
margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
),
Expanded(child: Text(description[2]))
],
),
margin: const EdgeInsets.fromLTRB(20, 5, 20, 5)),
// Partie Twitter
Container(
child: Row(
children: <Widget>[
Container(
child:
const Icon(DresskipIcons.DresskipIcon.twitter_square),
margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
),
Expanded(child: Text(description[3]))
],
),
margin: const EdgeInsets.fromLTRB(20, 5, 20, 5)),
],
));
}
// Widget pour afficher l'image
Widget buildImage() {
// use if is an image on the web with the link : final image = NetworkImage(imagePath);
return ClipOval(
child: Material(
color: Colors.transparent,
child: Ink.image(
image: const AssetImage("assets/undraw_female_avatar.png"),
fit: BoxFit.cover,
width: 128,
height: 128,
child: InkWell(onTap: onClicked),
)));
}
// Widget pour l'ajout de l'icone à coté de l'image
// Ici, il y a 2 fois buildCircle pour arrondir l'icone et ensuite mettre le trait blanc arrondi entre la photo et l'icône
Widget buildEditIcon(Color color) => buildCircle(
color: Colors.white,
all: 1,
child: buildCircle(
color: color,
all: 8,
child: Icon(Icons.add_a_photo, color: Colors.white, size: 20)));
// Widget permettant d'arrondir l'image
Widget buildCircle({
required Widget child,
required double all,
required Color color,
}) =>
ClipOval(
child: Container(
padding: EdgeInsets.all(all), color: color, child: child));
}
The parent page code
import 'package:dresskip/model/user_model.dart';
import 'package:flutter/material.dart';
import 'itemSection_widget.dart';
import 'profileSection_widget.dart';
import '/assets/constants.dart' as constants;
import 'dart:convert';
class AccountPage extends StatelessWidget {
const AccountPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
ProfileSection(
description: getUserInformation().properties,
onClicked: () async {}),
const Divider(
color: Color(constants.COLOR_BLUE_DRESSKIP),
thickness: 2,
indent: 50,
endIndent: 50,
),
ItemSection()
],
)));
}
getUserInformation() {
User myUser = User(username: "test", email: "[email protected]", properties: [
"22 yo\nFlexeur/Epicurien/Philanthrope\nJ'adore la vie\nEFREI Paris",
"instagram_test",
"facebook_test",
"twitter"
]);
return myUser;
}
}
There are 2 screenshots of my App.
The problem here, it's just loading 15 15 items (the first 30) and I can't load more data on scrolling.
EDIT
I find a way to resolv this problem. The attribute "shrinkwrap" block the possibility to scroll more because my widget which contains the gridview is into a Column Widget.
So i removed it, but just the Gridview is scrolling, I would like to do like Instagram's profil, when you scroll on your pictures, all the page scroll and not only the GridView.
Do you have an idea ?
CodePudding user response:
Firstly, I'm not aware of Instagram UI.
The problem is here with parent, while SingleChildScrollView
is the parent and you want to scroll the full page and generate GridItem based on it, therefore set we don't need to ScrollController
for GridView
instead use it on SingleChildScrollView
.
The code structure will be
- parent class generate data for GridView and will be
StatefulWidget
. - while there are two scrollable widgets so that
GridView < physics: NeverScrollableScrollPhysics(),>
- AccountPage:
SingleChildScrollView
will have that_scrollController
ItemSection
can beStatelessWidget
Full Code snippet with dummyHeaderWidget
import 'package:flutter/material.dart';
class AccountPage extends StatefulWidget {
const AccountPage({Key? key}) : super(key: key);
@override
State<AccountPage> createState() => _AccountPageState();
}
class _AccountPageState extends State<AccountPage> {
ScrollController _scrollController = ScrollController();
bool isLoading = true; //
int pageCount = 1;
List items = [];
//// ADDING THE SCROLL LISTINER
void _scrollListener() {
print(
"current ${_scrollController.offset} max: ${_scrollController.position.maxScrollExtent}");
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent &&
!_scrollController.position.outOfRange) {
setState(() {
print("comes to bottom $isLoading");
isLoading = true;
if (isLoading) {
print("RUNNING LOAD MORE");
pageCount = pageCount 1;
addItemsToList(pageCount);
}
});
}
}
addItemsToList(int page) {
//if (page < 5) {}
Item myItem = Item(
name: "test",
brand: "test",
color: ["0xFF39BDC8", "0xFFdb8abc", ""],
picture:
"https://images.pexels.com/photos/9676177/pexels-photo-9676177.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
//"https://scontent.fcdg2-1.fna.fbcdn.net/v/t1.6435-9/171944671_3950113148381954_7059062044076097927_n.jpg?_nc_cat=110&ccb=1-5&_nc_sid=09cbfe&_nc_ohc=gxbPXmRQmN8AX9V5Bx5&_nc_ht=scontent.fcdg2-1.fna&oh=ac2a57c8c1d1b0b01fcf131ac42c4023&oe=6190A9BF",
solo: false,
clean: true,
type: "test");
for (int i = (pageCount * 15) - 15; i < pageCount * 15; i ) {
items.add(myItem);
isLoading = false;
}
}
@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
addItemsToList(1);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: [
Container(
height: 300,
color: Colors.green,
),
const Divider(
thickness: 2,
indent: 50,
endIndent: 50,
),
ItemSection(
items: items,
),
],
),
),
);
}
}
class ItemSection extends StatelessWidget {
final List items;
const ItemSection({
Key? key,
required this.items,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
mainAxisSpacing: 0,
children: items.map((value) {
return Image.network(value.picture);
}).toList(),
);
}
}
class Item {
String name;
String brand;
List<String> color;
String picture;
bool solo;
bool clean;
String type;
Item({
required this.name,
required this.brand,
required this.color,
required this.picture,
required this.solo,
required this.clean,
required this.type,
});
}
Read the code, hope you will get concept and make changes on your project.
CodePudding user response:
Endless / Infinite Scroll GridView
This example uses a GridView.builder
& doesn't need a ScrollController
.
When the end of the current dataset is reached, it will request more data and rebuild the GridView
.
We can pad the end of the dataset with a special item. When this special item is built by GridView.builder
, it will:
- show a loading indicator
- request more data from datasource
- rebuild the GridView when data arrives
import 'package:flutter/material.dart';
class InfiniteScrollPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Infinite Scroll'),
),
body: EndlessGrid());
}
}
class EndlessGrid extends StatefulWidget {
@override
_EndlessGridState createState() => _EndlessGridState();
}
class _EndlessGridState extends State<EndlessGrid> {
NumberGenerator _numGen = NumberGenerator();
List<int> _data = [];
@override
void initState() {
super.initState();
_data = _numGen.nextPage();
}
@override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: _data.length 1, // pad data with an extra item at end
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 10, mainAxisSpacing: 10, crossAxisCount: 2),
itemBuilder: (context, i) {
if (i < _data.length) {
return gridItem(i);
}
else { // extra item will request next page & rebuild widget
getNextPage();
return CircularProgressIndicator();
}
},
);
}
Widget gridItem(int i) {
return Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.lightBlue
),
padding: EdgeInsets.all(5),
child: Text('$i'),
);
}
/// Request next page of data and call setState to rebuild GridView with
/// new data.
Future<void> getNextPage() async {
var _nextPage = await Future.delayed(Duration(seconds: 2), _numGen.addPage);
setState(() {
_data = _nextPage;
});
}
}
/// Mock data to fill GridView
class NumberGenerator {
static const PAGESIZE = 15;
List<int> dataset = [];
final int start;
NumberGenerator({this.start = 0});
List<int> addItem() {
dataset.add(lastItem 1);
return dataset;
}
List<int> addPage() {
dataset.addAll(nextPage());
return dataset;
}
int get lastItem => dataset.isNotEmpty ? dataset.last : start;
List<int> nextPage({int start, int size = PAGESIZE}) {
start ??= lastItem;
return List<int>.generate(size, (i) => start i 1);
}
}