Pagination with ListView.bulider in Flutter - flutter

I'm new at flutter and I have been searching for good results in pagination.
Pagination in flutter listview is a way to load the data when you reach the end of the list.
The pagination is used to load the data in part-wise.
Pagination in flutter listview divides the data in page manner like page 1 and page.
Need to load the list of data 10 items on each page
input:
Implement pagination
output:
Image:
https://i.stack.imgur.com/QKHc2.png

You can use loadmore package.
body: RefreshIndicator(
child: LoadMore(
isFinish: count >= 10,
onLoadMore: _loadMore,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Container(
child: Text(list[index].toString()),
alignment: Alignment.center,
);
},
itemCount: count,
),
whenEmptyLoad: false,
delegate: DefaultLoadMoreDelegate(),
textBuilder: DefaultLoadMoreTextBuilder.chinese,
),
onRefresh: _refresh,
),

You can use Flutter's NotificationListener<ScrollNotification>(...) widget.
Sample:
import 'package:flutter/material.dart';
class PaginatedListView extends StatefulWidget {
const PaginatedListView({
required this.onNext,
this.nextPageRatio = 1,
this.hasNextPage = false,
required this.child,
this.loadingIndicator,
super.key,
}) : assert(
nextPageRatio >= 0 && nextPageRatio <= 1,
'[nextPageRatio] should be between 0...1',
);
final VoidCallback onNext;
final double nextPageRatio;
final bool hasNextPage;
final SliverList child;
final Widget? loadingIndicator;
#override
State<PaginatedListView> createState() => _PaginatedListViewState();
}
class _PaginatedListViewState extends State<PaginatedListView> {
final ScrollController _controller = ScrollController();
double _currentMaxScrollExtent = 0;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_controller.position.maxScrollExtent == 0 && widget.hasNextPage) {
widget.onNext();
}
});
}
bool _onNotification(ScrollNotification notification) {
if (!widget.hasNextPage) {
return false;
}
final double maxScrollExtent =
notification.metrics.maxScrollExtent * widget.nextPageRatio;
if (notification.metrics.pixels >= maxScrollExtent &&
_currentMaxScrollExtent < maxScrollExtent) {
_currentMaxScrollExtent = maxScrollExtent;
widget.onNext();
}
return false;
}
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: _onNotification,
child: CustomScrollView(
controller: _controller,
physics: const AlwaysScrollableScrollPhysics(),
slivers: <Widget>[
widget.child,
if (widget.hasNextPage)
SliverToBoxAdapter(
child: Center(
child: widget.loadingIndicator ??
const CircularProgressIndicator(),
),
),
],
),
);
}
}

You can use the Pull To Refresh package for pagination in Flutter.

Related

Disable scrolling of CustomScrollView while scrolling without setState Flutter

I have multiple widget and lists within CustomScrollView and I would like to stop CustomScrollView to scroll while scrolling on some pixels bound condition.
I can use NeverScrollPhysics() to stop it but I don't want to use setState() function here because the CustomScrollview content with lists is big enough to make the screen laggy while reloading on scroll.
Also tried with Provider but the builder is providing only a child widget which is not working with sliver list.
Here is the code using setState() :
NotificationListener(
onNotification: (ScrollNotification notif) {
if(notif is ScrollUpdateNotification) {
if (canScroll && notif.metrics.pixels > 100) {
canScroll = false;
setState(() {});
}
}
if(notif is ScrollEndNotification) {
if(!canScroll) {
canScroll = true;
setState(() {});
}
}
return true;
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll ? BouncingScrollPhysics() : NeverScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(),
List(),
List(),
],
),
),
Is there a way to reload only the CustomScrollView without its child ? Otherwise any workaround to prevent scrolling in this case ?
Thanks for help
When the build method is called, all widgets in that build method will be rebuild except for const widgets, but const widget cannot accept dynamic arguments (only a constant values).
Riverpod provides a very good solution in this case,
With ProviderScope you can pass arguments by inherited widget instead of widget constructor (as when passing arguments using navigation) so the contractor can be const.
Example :
Data module
TLDR you need to use Freezed package or override the == operator and the hashCode almost always because of dart issue.
class DataClass {
final int age;
final String name;
const DataClass(this.age, this.name);
#override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DataClass && other.age == age && other.name == name;
}
#override
int get hashCode => age.hashCode ^ name.hashCode;
}
setting our ScopedProvider as a global variable
final dataClassScope = ScopedProvider<DataClass>(null);
The widget we use in our list
class MyChildWidget extends ConsumerWidget {
const MyChildWidget();
#override
Widget build(BuildContext context, ScopedReader watch) {
final data = watch(dataClassScope);
// Note for better optimaization
// in case you are sure the data you are passing to this widget wouldn't change
// you can just use StatelessWidget and set the data as:
// final data = context.read(dataClassScope);
// use ConsumerWidget (or Consumer down in this child widget tree) if the data has to change
print('widget with name\n${data.name} rebuild');
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
child: Text(
'Name : ${data.name}\nAge ${data.age}',
textAlign: TextAlign.center,
),
),
);
}
}
finally the main CustomScrollView widget
class MyMainWidget extends StatefulWidget {
const MyMainWidget();
#override
State<MyMainWidget> createState() => _MyMainWidgetState();
}
class _MyMainWidgetState extends State<MyMainWidget> {
bool canScroll = true;
void changeCanScrollState() {
setState(() => canScroll = !canScroll);
print('canScroll $canScroll');
}
final dataList = List.generate(
20,
(index) => DataClass(10 * index, '$index'),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
changeCanScrollState();
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll
? BouncingScrollPhysics()
: NeverScrollableScrollPhysics(),
slivers: [
for (int i = 0; i < dataList.length; i++)
ProviderScope(
overrides: [
dataClassScope.overrideWithValue(dataList[i]),
],
child: const MyChildWidget(),
),
],
),
),
);
}
}
Don't forget to wrap the MaterialApp with ProviderScope.
runApp(
ProviderScope(
child: MyApp(),
),
);
Try this solution use const constructor for child widget so it won't rebuild unless widget changed
class MyHomePage extends StatelessWidget {
ValueNotifier<ScrollPhysics> canScroll =
ValueNotifier(const BouncingScrollPhysics());
MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (canScroll.value.runtimeType == BouncingScrollPhysics &&
notif.metrics.pixels > 100) {
canScroll.value = const NeverScrollableScrollPhysics();
debugPrint("End false");
}
}
if (notif is ScrollEndNotification) {
if (canScroll.value.runtimeType == NeverScrollableScrollPhysics) {
debugPrint("End");
Future.delayed(const Duration(milliseconds: 300),
() => canScroll.value = const BouncingScrollPhysics());
debugPrint("End1");
}
}
return true;
},
child: ValueListenableBuilder(
valueListenable: canScroll,
builder:
(BuildContext context, ScrollPhysics scrollType, Widget? child) =>
CustomScrollView(
physics: scrollType,
slivers: [
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.black,
),
),
SliverToBoxAdapter(
child: Column(
children: [
Container(
height: 100,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
],
),
),
],
),
),
),
);
}
}
Are you just need to stop the user from scrolling it? I think you can try to controller the list to a fixed position by using jumoTo.
...
final _controller = ScrollController();
#override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (notif.metrics.pixels > 100) {
_controller.jumpTo(100)
}
}
return true;
},
child: CustomScrollView(
controller: _controller,
...

FutureBuilder The method 'findRenderObject' was called on null

I want to render the position of a RichText built by a FutureBuilder as the code below, I used the WidgetsBinding.instance.addPostFrameCallback in the initState() but I got an error: The method 'findRenderObject' was called on null., I tried this approach without FutureBuilder works fine, I do not know how to solve this with FutureBuilder
class BookScreen extends StatefulWidget {
int bookId;
BookScreen(this.bookId);
#override
_BookScreenState createState() => _BookScreenState();
}
class _BookScreenState extends State<BookScreen> {
final GlobalKey _itemKey = GlobalKey();
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {findRichText();});
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Provider.of<Book>(context, listen: false)
.getBookDetail(widget.bookId),
builder: (ctx, snapshot) => snapshot.connectionState == ConnectionState.waiting
? Center(
child: CircularProgressIndicator(),
)
: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: RichText(
key: _itemKey, // Here is the global key
text: TextSpan(
children: _getTextSpan(snapshot.data),
),
),
),
],
),
);
void findRichText() {
var richText = _itemKey.currentContext.findRenderObject() as RenderParagraph;
print(richText.localToGlobal(Offset.zero));
}
It is possible to query the text position after it renders.
For example, you can move ListView to a separate widget. When postframe callback is called, the text will already exist so you'll get its position
class _BookScreenState extends State<BookScreen> {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: ...,
builder: (ctx, snapshot) =>
snapshot.connectionState == ConnectionState.waiting
? Center(child: CircularProgressIndicator())
: BooksList(data: snapshot.data),
);
}
}
class BooksList extends StatefulWidget {
final BooksListData data;
BooksList({#required this.data});
#override
_BooksListState createState() => _BooksListState();
}
class _BooksListState extends State<BooksList> {
final GlobalKey _itemKey = GlobalKey();
#override
Widget build(BuildContext context) {
return ListView(
children: [
RichText(
key: _itemKey,
text: TextSpan(
children: _getTextSpan(widget.data),
),
),
],
);
}
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
findRichText();
});
}
void findRichText() {
var richText = _itemKey.currentContext.findRenderObject() as RenderParagraph;
print(richText.localToGlobal(Offset.zero));
}
}
However this approach complicates the code and doesn't seem reliable.
Alternatively, if you want scrolling to listview item, you can use scrollable_positioned_list package. It provides more declarative api:
final ItemScrollController itemScrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemCount: ...,
itemBuilder: (context, index) => ...,
itemScrollController: itemScrollController,
);
itemScrollController.jumpTo(
index: 100,
alignment: 0.5,
);

Best practices for implementing an infinite scroll GridView in Flutter?

I want to create a GridView that displays items which will be fetching from server by offset. I load only 10 items in GridView, and after user scroll reached to 10, I will load more 10 items. What are the best practices for implementing an infinite scroll GridView in Flutter?
You need to add the ScrollController for the scrolling detection at the bottom for the ListView and GridView. As you need the GridView i have created the ScrollController listner and added to the GridView's contollerfor the detection of the scroll. I have created the demo of it , please check it once. At first time it load the 10 items and when list comes to the bottom then it add more 10 items in it.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HomeState();
}
}
class HomeState extends State<HomeScreen> {
List dataList = new List<int>();
bool isLoading = false;
int pageCount = 1;
ScrollController _scrollController;
#override
void initState() {
super.initState();
////LOADING FIRST DATA
addItemIntoLisT(1);
_scrollController = new ScrollController(initialScrollOffset: 5.0)
..addListener(_scrollListener);
}
Widget build(BuildContext context) {
return MaterialApp(
title: 'Gridview',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.red,
accentColor: Color(0xFFFEF9EB),
),
home: Scaffold(
appBar: new AppBar(),
body: GridView.count(
controller: _scrollController,
scrollDirection: Axis.vertical,
crossAxisCount: 2,
mainAxisSpacing: 10.0,
physics: const AlwaysScrollableScrollPhysics(),
children: dataList.map((value) {
return Container(
alignment: Alignment.center,
height: MediaQuery.of(context).size.height * 0.2,
margin: EdgeInsets.only(left: 10.0, right: 10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
),
child: Text("Item ${value}"),
);
}).toList(),
)));
}
//// ADDING THE SCROLL LISTINER
_scrollListener() {
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;
addItemIntoLisT(pageCount);
}
});
}
}
////ADDING DATA INTO ARRAYLIST
void addItemIntoLisT(var pageCount) {
for (int i = (pageCount * 10) - 10; i < pageCount * 10; i++) {
dataList.add(i);
isLoading = false;
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
And output of the above program as follow
class AllOrdersPage extends StatefulWidget {
#override
_AllOrdersPageState createState() => _AllOrdersPageState();
}
class _AllOrdersPageState extends State<AllOrdersPage> {
List<OrderDatum> ordersList;
ScrollController _scrollController = ScrollController();
int skip = 0;
bool shouldLoadMore = true;
Future<OrdersResponse> future;
#override
void initState() {
super.initState();
ordersList = [];
future = getAllOrders(skip); //load data for first time
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) { //Check whether user scrolled to last position
if (shouldLoadMore) {
setState(() {
skip += ordersList.length;
future = getAllOrders(skip); //load more data
});
}
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<OrdersResponse>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasError)
return ErrorText('${snapshot.error.toString()}');
if (snapshot.hasData) {
skip = snapshot.data.skip;
if (snapshot.data.limit + snapshot.data.skip >=
snapshot.data.total) {
shouldLoadMore = false;
}
snapshot.data.data.forEach((element) {
if (!ordersList.contains(element)) ordersList.add(element);
});
if (skip == 0 && ordersList.isEmpty) {
return ErrorText('No orders.');
}
return Scrollbar(
controller: _scrollController,
isAlwaysShown: true,
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
childAspectRatio: 2.5,
mainAxisSpacing: 8),
controller: _scrollController,
padding: const EdgeInsets.all(8),
itemBuilder: (BuildContext context, int index) {
if (index == ordersList.length) {
return shouldLoadMore
? Center(child: CircularProgressIndicator())
: Container();
}
return Container(
width: MediaQuery.of(context).size.width,
child: OrderCard(ordersList[index]));
},
itemCount: ordersList.length + 1,
),
);
}
return Loader();
});
}
}
Thanks

how to do pagination in GridView (Flutter)

I want to implement pagination in GridView I use GridView.builder I want to download 10 by 10 items when the user reaches the last row
You can do this using a NotificationListener. As a simple demonstration it will increase the length of your GridView whenever it reaches end of page :
var items_number = 10 ;
return NotificationListener<ScrollNotification>(
onNotification: (scrollNotification){
if(scrollNotification.metrics.pixels == scrollNotification.metrics.maxScrollExtent){
setState(() {
items_number += 10 ;
});
}
},
child: GridView.builder(
itemCount: items_number,
itemBuilder: (context, index) {
//.... the reminder of your code
}
),
);
I also needed this but couldn't find any widget for the gridview pagination, so I tried to make a component based on #Mazin Ibrahim's answer below. It seems to be working but not sure if it is the right way to do this.
typedef Future<bool> OnNextPage(int nextPage);
class GridViewPagination extends StatefulWidget {
final int itemCount;
final double childAspectRatio;
final OnNextPage onNextPage;
final Function(BuildContext context, int position) itemBuilder;
final Widget Function(BuildContext context) progressBuilder;
GridViewPagination({
this.itemCount,
this.childAspectRatio,
this.itemBuilder,
this.onNextPage,
this.progressBuilder,
});
#override
_GridViewPaginationState createState() => _GridViewPaginationState();
}
class _GridViewPaginationState extends State<GridViewPagination> {
int currentPage = 1;
bool isLoading = false;
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification sn) {
if (!isLoading && sn is ScrollUpdateNotification && sn.metrics.pixels == sn.metrics.maxScrollExtent) {
setState(() {
this.isLoading = true;
});
widget.onNextPage?.call(currentPage++)?.then((bool isLoaded) {
setState(() {
this.isLoading = false;
});
});
}
return true;
},
child: CustomScrollView(
slivers: <Widget>[
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 1,
mainAxisSpacing: 1,
crossAxisCount: 2,
childAspectRatio: widget.childAspectRatio,
),
delegate: SliverChildBuilderDelegate(
widget.itemBuilder,
childCount: widget.itemCount,
addAutomaticKeepAlives: true,
addRepaintBoundaries: true,
addSemanticIndexes: true,
),
),
if (isLoading)
SliverToBoxAdapter(
child: widget.progressBuilder?.call(context) ?? _defaultLoading(),
),
],
),
);
}
Widget _defaultLoading() {
return Container(
padding: EdgeInsets.all(15),
alignment: Alignment.center,
child: CircularProgressIndicator(),
);
}
}
Example -
GridViewPagination(
itemCount: 10,
childAspectRatio: 1,
itemBuilder: _buildGridItem,
onNextPage: (int nextPage) {
return fetchData();
},
)
create a Scroll controller
ScrollController _scrollController = new ScrollController();
add a scroll event listener
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
// Bottom poistion
}
});
}
Now just need to set the controller in your GridView, ListViewand ...
GridView.builder(
controller: _scrollController,
));
You can use this plugin here: Paging. Wrap your GridView inside of it and tell me if this works!

How to create a listview that makes centering the desired element

I'm doing something similar to this video: https://youtu.be/fpqHUp4Sag0
With the following code I generate the listview but when using the controller in this way the element is located at the top of the listview and I need it to be centered
Widget _buildLyric() {
return ListView.builder(
itemBuilder: (BuildContext context, int index) => _buildPhrase(lyric[index]),
itemCount: lyric.length,
itemExtent: 90.0,
controller: _scrollController,
);
}
void goToNext() {
i += 1;
if (i == lyric.length - 1) {
setState(() {
finishedSync = true;
});
}
syncLyric.addPhrase(
lyric[i], playerController.value.position.inMilliseconds);
_scrollController.animateTo(i*90.0,
curve: Curves.ease, duration: new Duration(milliseconds: 300));
}
Using center and shrinkWrap: true
Center(
child: new ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return Text("Centered item");
},
),
);
You're going to have to do some math! (Nooooo, not the mathssssss).
It seems as though your goToNext() function is called while the app is running, rather than during build time. This makes it a little easier - you can simply use context.size. Otherwise you'd have to use a LayoutBuilder and maxHeight.
You can then divide this in two to get the half, then add/subtract whatever you need to get your item positioned how you want (since you've specified it's height as 90 in the example, I assume you could use 45 to get what you want).
Here's an example you can paste into a file to run:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(Wid());
class Wid extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Scrolling by time"),
),
body: new Column(
children: <Widget>[
Expanded(child: Container()),
Container(
height: 300.0,
color: Colors.orange,
child: ScrollsByTime(
itemExtent: 90.0,
),
),
Expanded(child: Container()),
],
),
),
);
}
}
class ScrollsByTime extends StatefulWidget {
final double itemExtent;
const ScrollsByTime({Key key, #required this.itemExtent}) : super(key: key);
#override
ScrollsByTimeState createState() {
return new ScrollsByTimeState();
}
}
class ScrollsByTimeState extends State<ScrollsByTime> {
final ScrollController _scrollController = new ScrollController();
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (timer) {
_scrollController.animateTo(
(widget.itemExtent * timer.tick) - context.size.height / 2.0 + widget.itemExtent / 2.0,
duration: Duration(milliseconds: 300),
curve: Curves.ease,
);
});
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Center(child: Text("Item $index"));
},
itemExtent: widget.itemExtent,
controller: _scrollController,
);
}
}
I had a similar problem, but with the horizontal listview. You should use ScrollController and NotificationListener. When you receive endScroll event you should calculate offset and use scroll controller animateTo method to center your items.
class SwipeCalendarState extends State<SwipeCalendar> {
List<DateTime> dates = List();
ScrollController _controller;
final itemWidth = 100.0;
#override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
for (var i = 1; i < 365; i++) {
var date = DateTime.now().add(Duration(days: i));
dates.add(date);
}
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
height: 200,
child: Stack(
children: <Widget>[buildListView()],
),
);
}
void _onStartScroll(ScrollMetrics metrics) {
}
void _onUpdateScroll(ScrollMetrics metrics){
}
void _onEndScroll(ScrollMetrics metrics){
print("scroll before = ${metrics.extentBefore}");
print("scroll after = ${metrics.extentAfter}");
print("scroll inside = ${metrics.extentInside}");
var halfOfTheWidth = itemWidth/2;
var offsetOfItem = metrics.extentBefore%itemWidth;
if (offsetOfItem < halfOfTheWidth) {
final offset = metrics.extentBefore - offsetOfItem;
print("offsetOfItem = ${offsetOfItem} offset = ${offset}");
Future.delayed(Duration(milliseconds: 50), (){
_controller.animateTo(offset, duration: Duration(milliseconds: 100), curve: Curves.linear);
});
} else if (offsetOfItem > halfOfTheWidth){
final offset = metrics.extentBefore + offsetOfItem;
print("offsetOfItem = ${offsetOfItem} offset = ${offset}");
Future.delayed(Duration(milliseconds: 50), (){
_controller.animateTo(offset, duration: Duration(milliseconds: 100), curve: Curves.linear);
});
}
}
Widget buildListView() {
return NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollStartNotification) {
_onStartScroll(scrollNotification.metrics);
} else if (scrollNotification is ScrollUpdateNotification) {
_onUpdateScroll(scrollNotification.metrics);
} else if (scrollNotification is ScrollEndNotification) {
_onEndScroll(scrollNotification.metrics);
}
},
child: ListView.builder(
itemCount: dates.length,
controller: _controller,
scrollDirection: Axis.horizontal,
itemBuilder: (context, i) {
var item = dates[i];
return Container(
height: 100,
width: itemWidth,
child: Center(
child: Text("${item.day}.${item.month}.${item.year}"),
),
);
}),
);
}
}
IMO the link you have posted had some wheel like animation. Flutter provides this type of animation with ListWheelScrollView and rest can be done with the fade in animation and change in font weight with ScrollController.