Skip to content

Instantly share code, notes, and snippets.

@chaudharydeepanshu
Created August 5, 2022 10:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chaudharydeepanshu/d12e8303f1d3920a8c8a6f4e8d0e3ba4 to your computer and use it in GitHub Desktop.
Save chaudharydeepanshu/d12e8303f1d3920a8c8a6f4e8d0e3ba4 to your computer and use it in GitHub Desktop.
scroll view issue for calendar view
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(
useMaterial3: true,
),
darkTheme: ThemeData.dark(
useMaterial3: true,
),
themeMode: ThemeMode.system,
home: const Home(),
);
}
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
late ScrollController _controller;
// This points always to the mid-element in _list
late int _initialIndex;
// This should work with 3, 7, 11, ... odd elements. Mind the pattern!!!
List<int> list1 = [-1, -2];
List<int> list2 = [0, 1, 2];
List<int> list = [-2, -1, 0, 1, 2];
late List<Widget> _pages;
List<Widget> _newPages = <Widget>[];
late List<GlobalKey> listOfGlobalKeys;
@override
void initState() {
super.initState();
listOfGlobalKeys = List.generate(list.length, (index) {
return GlobalKey();
});
_newPages = List.generate(list1.length, (index) {
final GlobalKey key = listOfGlobalKeys[index];
List<DateTime> allDatesOfCalendarMonth = getDatesForACalendarMonthAsUTC(
dateTime: DateTime(2022, 8 + list1[index], 1));
return Column(
children: [
Text(months[DateTime(2022, 8 + list1[index], 1).month - 1] +
" " +
DateTime(2022, 8 + list1[index], 1).year.toString()),
GridView.builder(
key: key,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 1,
crossAxisCount: 7,
mainAxisExtent: 40,
),
itemCount: 42,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, i) {
return DataWidget(
dataIndex: allDatesOfCalendarMonth[i],
gridIndex: DateTime(2022, 8 + list1[index], 1),
);
},
),
],
);
});
_pages = List.generate(list2.length, (index) {
final GlobalKey key = listOfGlobalKeys[index + list1.length];
List<DateTime> allDatesOfCalendarMonth = getDatesForACalendarMonthAsUTC(
dateTime: DateTime(2022, 8 + list2[index], 1));
return Column(
children: [
Text(months[DateTime(2022, 8 + list2[index], 1).month - 1] +
" " +
DateTime(2022, 8 + list2[index], 1).year.toString()),
GridView.builder(
key: key,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 1,
crossAxisCount: 7,
mainAxisExtent: 40,
),
itemCount: 42,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, i) {
return DataWidget(
dataIndex: allDatesOfCalendarMonth[i],
gridIndex: DateTime(2022, 8 + list2[index], 1),
);
},
),
],
);
});
// Calculate mid.
_initialIndex = 0;
_controller = ScrollController();
// This is where we listen to changes.
_controller.addListener(() {
final keyContext =
listOfGlobalKeys[list.indexOf(_initialIndex)].currentContext;
// Get index according to the direction
// _controller.page! > _initialIndex => swiping to the right, going to the left / previous element
// _controller.page! < _initialIndex => swiping to the left, going to the right / next element
if (keyContext != null) {
final box = keyContext.findRenderObject() as RenderBox;
final index =
(_controller.offset / box.size.width).round() > _initialIndex
? (_controller.offset / box.size.width).round().floor()
: (_controller.offset / box.size.width).round().ceil();
if (index == _initialIndex) {
return;
}
if (index < _initialIndex) {
_prev();
} else if (index > _initialIndex) {
_next();
}
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
// Update list and jump to the middle element
void _next() {
setState(() {
list
..removeAt(0)
..insert(list.length, list.last + 1);
_initialIndex++;
final GlobalKey key = GlobalKey();
listOfGlobalKeys
..removeAt(0)
..insert(listOfGlobalKeys.length, key);
List<DateTime> allDatesOfCalendarMonth = getDatesForACalendarMonthAsUTC(
dateTime: DateTime(2022, 8 + list.last, 1));
_pages.add(
Column(
children: [
Text(months[DateTime(2022, 8 + list.last, 1).month - 1] +
" " +
DateTime(2022, 8 + list.last, 1).year.toString()),
GridView.builder(
key: key,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 1,
crossAxisCount: 7,
mainAxisExtent: 40,
),
itemCount: 42,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, i) {
return DataWidget(
dataIndex: allDatesOfCalendarMonth[i],
gridIndex: DateTime(2022, 8 + list.last, 1),
);
},
),
],
),
);
});
}
// Update list and jump to the middle element
void _prev() {
setState(() {
list
..insert(0, list.first - 1)
..removeLast();
_initialIndex--;
final GlobalKey key = GlobalKey();
listOfGlobalKeys
..insert(0, key)
..removeLast();
List<DateTime> allDatesOfCalendarMonth = getDatesForACalendarMonthAsUTC(
dateTime: DateTime(2022, 8 + list.first, 1));
_newPages.add(
Column(
children: [
Text(months[DateTime(2022, 8 + list.first, 1).month - 1] +
" " +
DateTime(2022, 8 + list.first, 1).year.toString()),
Container(
color: Colors.pink,
child: GridView.builder(
key: key,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 1,
crossAxisCount: 7,
mainAxisExtent: 40,
),
itemCount: 42,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, i) {
return DataWidget(
dataIndex: allDatesOfCalendarMonth[i],
gridIndex: DateTime(2022, 8 + list.first, 1),
);
},
),
),
],
),
);
});
}
List months = [
'jan',
'feb',
'mar',
'apr',
'may',
'jun',
'jul',
'aug',
'sep',
'oct',
'nov',
'dec'
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2,
title: const Text("Data Grid"),
),
body: Column(
children: [
SizedBox(
height: 500,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
},
),
child: CustomScrollView(
scrollDirection: Axis.horizontal,
center: ValueKey(0),
controller: _controller,
/// <-- Here...
physics: PageScrollPhysics(),
slivers: [
SliverFillViewport(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _newPages[index];
},
childCount: _newPages.length,
),
),
SliverFillViewport(
key: ValueKey(0),
/// <-- and here...
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _pages[index];
},
childCount: _pages.length,
),
),
],
),
),
),
],
),
);
}
}
class DataWidget extends StatelessWidget {
const DataWidget({Key? key, required this.dataIndex, required this.gridIndex})
: super(key: key);
final DateTime dataIndex;
final DateTime gridIndex;
@override
Widget build(BuildContext context) {
return Row(
children: [
const Expanded(
child: SizedBox(),
),
OutlinedButton(
style: OutlinedButton.styleFrom(
minimumSize: Size.zero,
padding: EdgeInsets.zero,
foregroundColor: Theme.of(context).colorScheme.onSurface,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
side: BorderSide.none,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
onPressed: dataIndex.month == gridIndex.month
? () {
//do something
}
: null,
child: SizedBox(
width: 40,
child: Container(
margin: const EdgeInsets.all(4.0),
child: Ink(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).dividerColor, width: 1),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Center(
child: Text(
dataIndex.day.toString(),
style: const TextStyle(),
),
),
),
),
),
),
const Expanded(
child: SizedBox(),
),
],
);
}
}
List<DateTime> getDatesForACalendarMonthAsUTC({required DateTime dateTime}) {
List<DateTime> calendarMonthDaysAsUTC = [];
DateTime currentDateTime = DateTime.utc(dateTime.year, dateTime.month, 1);
DateTime firstDayOfMonthAsUTC =
DateTime.utc(currentDateTime.year, currentDateTime.month, 1);
DateTime lastDayOfMonthAsUTC =
getLastDayOfAMonth(currentDateTime: currentDateTime);
List<DateTime> datesFirstToLastDayOfMonthAsUTC =
getDaysInBetweenIncludingStartEndDate(
startDateTime: firstDayOfMonthAsUTC,
endDateTime: lastDayOfMonthAsUTC);
calendarMonthDaysAsUTC = List.generate(datesFirstToLastDayOfMonthAsUTC.length,
(index) => datesFirstToLastDayOfMonthAsUTC[index]);
int firstDayOfMonthWeekDay = firstDayOfMonthAsUTC.weekday;
for (int i = 1;
i <= firstDayOfMonthWeekDay && firstDayOfMonthWeekDay != 7;
i++) {
calendarMonthDaysAsUTC.insert(
0, firstDayOfMonthAsUTC.subtract(Duration(days: i)));
}
int daysLeftAfterMonthEndDate = 42 - calendarMonthDaysAsUTC.length;
for (int i = 1; i <= daysLeftAfterMonthEndDate; i++) {
calendarMonthDaysAsUTC.add(lastDayOfMonthAsUTC.add(Duration(days: i)));
}
return calendarMonthDaysAsUTC;
}
List<DateTime> getDaysInBetweenIncludingStartEndDate(
{required DateTime startDateTime, required DateTime endDateTime}) {
// Converting dates provided to UTC
// So that all things like DST don't affect subtraction and addition on dates
DateTime startDateInUTC =
DateTime.utc(startDateTime.year, startDateTime.month, startDateTime.day);
DateTime endDateInUTC =
DateTime.utc(endDateTime.year, endDateTime.month, endDateTime.day);
// Created a list to hold all dates
List<DateTime> daysInFormat = [];
// Starting a loop with the initial value as the Start Date
// With an increment of 1 day on each loop
// With condition current value of loop is smaller than or same as end date
for (DateTime i = startDateInUTC;
i.isBefore(endDateInUTC) || i.isAtSameMomentAs(endDateInUTC);
i = i.add(const Duration(days: 1))) {
// Converting back UTC date to Local date before inserting in list
// You can keep in UTC format depending on your case
if (startDateTime.isUtc) {
daysInFormat.add(i);
} else {
daysInFormat.add(DateTime(i.year, i.month, i.day));
}
}
return daysInFormat;
}
DateTime getLastDayOfAMonth({required DateTime currentDateTime}) {
// Getting the 15th-day date of the month for the date provided
DateTime fifteenthDayOfMonth =
DateTime(currentDateTime.year, currentDateTime.month, 15);
// Converting the 15th-day date to UTC
// So that all things like DST don't affect subtraction and addition on date
DateTime twentiethDayOfMonthInUTC = fifteenthDayOfMonth.toUtc();
// Getting a random date of next month by adding 20 days to twentiethDayOfMonthInUTC
// Adding number 20 to any month 15th-day will definitely give a next month date
DateTime nextMonthRandomDateInUTC =
twentiethDayOfMonthInUTC.add(const Duration(days: 20));
DateTime nextMonthRandomDateZeroDayInUTC = DateTime.utc(
nextMonthRandomDateInUTC.year, nextMonthRandomDateInUTC.month, 0);
// Now getting the 0th day date of the next month
// This will give us the current month last date
DateTime nextMonthRandomDateZeroDayInLocal = DateTime(
nextMonthRandomDateInUTC.year, nextMonthRandomDateInUTC.month, 0);
DateTime lastDayOfAMonth;
if (currentDateTime.isUtc) {
lastDayOfAMonth = nextMonthRandomDateZeroDayInUTC;
} else {
lastDayOfAMonth = nextMonthRandomDateZeroDayInLocal;
}
return lastDayOfAMonth;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment