Created
August 5, 2022 10:11
-
-
Save chaudharydeepanshu/d12e8303f1d3920a8c8a6f4e8d0e3ba4 to your computer and use it in GitHub Desktop.
scroll view issue for calendar view
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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