How to catch a scroll event in GridView? - flutter

I need somehow to catch a scroll event in the GridView to update the _length variable. So on scroll the _length is increasing and more content will be displayed in the GridView. How to make it?
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _length = 10;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: _contentGridView(),
),
);
}
Widget _contentGridView() {
return GridView.builder(
padding: EdgeInsets.all(5),
itemCount: _length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
),
itemBuilder: (context, index) {
int i = getRandomId();
return Image.network('https://i.pravatar.cc/1000?img=1');
});
}
}

i belive you want pagination.
but according to your question answer will be something like , let me know something else you wanted.
import 'package:flutter/material.dart';
class MyAppG extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyAppG> {
late int _length;
late ScrollController controller;
#override
void initState() {
super.initState();
_length = 10;
controller = ScrollController();
controller.addListener(() {
if (controller.hasClients) {
if (controller.position.maxScrollExtent == controller.offset) {
setState(() {
_length += 3;
});
}
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: _contentGridView(),
),
);
}
Widget _contentGridView() {
return GridView.builder(
controller: controller,
padding: EdgeInsets.all(5),
itemCount: _length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
),
itemBuilder: (context, index) {
return Container(
height: 50,
color: index % 3 == 0 ? Colors.cyanAccent : Colors.amberAccent,
child: Center(
child: Text("$index"),
),
);
});
}
}
```

Related

Detect scrolling to end of a list with NotificationListener

I have a list view that show limited number of items. When the user scroll to the end I wanted to load next batch of items to the list.
I decided to use "NotificationListener" for this.
With the following code I was able to detect user reaching the end .
# #override
Widget build(BuildContext context) {
return Container(
height: 430,
child: NotificationListener<ScrollNotification>(
child: ListView.builder(
controller: controller,
physics: const AlwaysScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: widget.resList.length,
itemBuilder: (BuildContext ctx, int index) {
return GestureDetector(
onTap: null,
child: ReservationListTile(),
);
},
),
onNotification: (ScrollNotification notification) {
print(notification.metrics.pixels);
if (notification.metrics.atEdge) {
if (notification.metrics.pixels == 0) {
print('At left');
} else {
print('At right');
}
}
return true;
},
),
);
}
What I hoped was, when the user reach the end of the list she will swipe the list again and there is a trigger to detect that and I would be able to load the next set of items.
The problem I have is when the user reached the end, the edge event get triggered multiple times.
Is there a way to detect the user pulling the list back?
I had a very similar requirement, and I used a listener to detect when the user reached the end of the list. I actually decided not to wait until the user reaches the very end to provide a smoother user experience, so for example at 80% I already add the new items to the list.
See the following code. If you change _scrollThreshold to 0.75 for example, you will see the print statement executing. This is the place where you can add the new items.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) => const MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
static const _itemCount = 32;
static const _scrollThreshold = 1.00;
late final ScrollController _scrollController;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
}
#override
void dispose() {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => SizedBox(
height: 400,
child: Center(
child: ListView.builder(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: _itemCount,
itemBuilder: (BuildContext ctx, int index) => Card(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Center(child: Text('Item $index'))),
))));
void _scrollListener() {
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * _scrollThreshold &&
!_scrollController.position.outOfRange) {
print('Scroll position is at ${_scrollThreshold * 100}%.');
}
}
}

How to scroll two SingleChildScrollView same time in Flutter?

I want to scroll two SingleChildScrollView same time vertically.
This code works for both SingleChildScrollViews.
import 'package:flutter/material.dart';
class ScrollTestPage extends StatefulWidget {
const ScrollTestPage({Key? key}) : super(key: key);
#override
State<ScrollTestPage> createState() => _ScrollTestPageState();
}
class _ScrollTestPageState extends State<ScrollTestPage> {
final _controller1 = ScrollController();
final _controller2 = ScrollController();
#override
void initState() {
super.initState();
_controller1.addListener(listener1);
_controller2.addListener(listener2);
}
var _flag1 = false;
var _flag2 = false;
void listener1() {
if (_flag2) return;
_flag1 = true;
_controller2.jumpTo(_controller1.offset);
_flag1 = false;
}
void listener2() {
if (_flag1) return;
_flag2 = true;
_controller1.jumpTo(_controller2.offset);
_flag2 = false;
}
#override
void dispose() {
super.dispose();
_controller1.removeListener(listener1);
_controller2.removeListener(listener2);
_controller1.dispose();
_controller2.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView.builder(
controller: _controller1,
itemBuilder: (context, i) {
return Container(
margin: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Text('First List View $i'),
);
},
itemCount: 100,
),
),
Expanded(
child: ListView.builder(
controller: _controller2,
itemBuilder: (context, i) {
return Container(
margin: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Text('Second List View $i'),
);
},
itemCount: 100,
),
),
],
),
);
}
}
You can set the controller: parameter of both SingleChildScrollView widgets to the same controller. The controller has to be initialized like this:
final ScrollController _scrollController = ScrollController();

how can i Call event after scroll controller attached

#override
void initState()
{
super.initState();
_scrollController = ScrollController(initialScrollOffset: 0);
_scrollController.addListener((_scrollControllerInitiated));
}
void _scrollControllerInitiated()
{
_scrollController.jumpto(1000);
}
need to call _scrollControllerInitiated method after scroll controller attached
what _scrollController.addListener does is listen to any scroll event and call the registered callback function. If you wall the scroll to jump to a certain offSet after being attached to the ListView or any scrollable widget you should use WidgetsBinding.instance.addPostFrameCallback to register a callback function after the widgets have been built. Check the code below
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
_MyApp createState() => _MyApp();
}
class _MyApp extends State<MyApp> {
ScrollController _scrollController;
void _scrollControllerInitiated() {
_scrollController.animateTo(200,
duration: Duration(milliseconds: 500), curve: Curves.ease);
}
Widget _itemBuilder(BuildContext context, int index) {
return GestureDetector(
onTap: () => setState(() {
print("hello $index");
}),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: Text("Box $index"),
),
);
}
void initState() {
super.initState();
_scrollController = ScrollController();
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollControllerInitiated();
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Scroll Test"),
),
body: ListView.builder(
itemBuilder: _itemBuilder,
itemCount: 200,
controller: _scrollController,
),
),
);
}
}
Try this.
void _scrollControllerInitiated() {
Future(() => _scrollController.jumpTo(1000));
}

Flutter/Dart - Nested Horizontal and Verticle PageViewBuilders?

Upon viewing each page in a horizontal PageView.builder I'd like to call a vertical PageView.builder to supply each page with its own vertical list of replies. What's the best way to do this?
class StageBuilder extends StatelessWidget {
final List<SpeakContent> speakcrafts;
StageBuilder(this.speakcrafts);
final PageController controller = PageController(initialPage: 0);
#override
Widget build(context) {
return PageView.builder(
controller: controller,
itemCount: speakcrafts.length,
itemBuilder: (context, int currentIndex) {
return createViewItem(speakcrafts[currentIndex], context, currentIndex);
},
);
}
Widget createViewItem(SpeakContent speakcraft, BuildContext context, int currentIndex) {
return Scaffold(
body: (some stuff),
);
}
}
The problem is that you are putting a scaffold inside your PageView when it should be otherwise. See the example below:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 20),
child: PageViewDemo(),
),
),
);
}
}
class PageViewDemo extends StatefulWidget {
#override
_PageViewDemoState createState() => _PageViewDemoState();
}
class _PageViewDemoState extends State<PageViewDemo> {
PageController _controller = PageController(
initialPage: 0,
);
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return PageView(
controller: _controller,
children: [
MyPage1Widget(),
MyPage2Widget(),
MyPage3Widget(),
],
);
}
}

Using jumpto and sticky_headers package causes UI issue

I created a new flutter project and added the sticky_headers package.
Afterwards I implemented a list builder that has a ScrollController.
I call setState 3 times at a 5 seconds interval and modify a global offset variable which will be used on the build method to jump the list to a new offset (using SchedulerBinding.instance.addPostFrameCallback).
The problem is that after I call the jumpto method, the headers are messed up ( if I manual scroll the headers will come back to a normal position).
Example:
The code:
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:sticky_headers/sticky_headers.dart';
double offset = 0;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController _scrollController = ScrollController();
#override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 5)).then((_) => setState(() {
offset = 3000;
})).then((_) async => await Future.delayed(Duration(seconds: 5))).then((_) => setState(() {
offset = 1000;
})).then((_) async => await Future.delayed(Duration(seconds: 5))).then((_) => setState(() {
offset = 5000;
}));
}
#override
Widget build(BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback((_) {
print(offset);
_scrollController.jumpTo(offset);
});
return Scaffold(body: Container(padding: EdgeInsets.only(top: 20),child: _buildList(context)));
}
Widget _buildList(BuildContext context) {
return NotificationListener<ScrollNotification>(
child: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (BuildContext context, int sectionIndex) => StickyHeader(
header: Container(
height: 50,
alignment: Alignment.center,
color: Colors.blue,
child: Text("$sectionIndex")),
content: ListView.builder(
itemCount: 10,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, rowIndex) => Container(
color: Colors.white,
child: Text("$sectionIndex"))))),
onNotification: (ScrollNotification scrollNotification) {
offset = _scrollController.offset;
print(offset);
return true;
});
}
}