flutter 自定义日历选择
禁止转载、抄袭
功能需求
实现后是长这样的(因为项目要以底部弹窗显示,也可以整个UI自定义) 要代码的私信我(看人品回复) 项目需求:
- 星期一在第一,星期日在最后
- 一开始显示的是当前月份
- 有startTime和endTime的限制,所以并不是每个日期都能选择
- 选择全部工作日按钮,意思是除了星期六日和第三点的条件都选择
- 已选统计选了多少天,下一步把选择的传到下个页面
- 没有说需不需要滑动切换月份(我是没做了)
- 在第3点外的月份不能点击全选
干就完事了
我看了日历第三方库,有些符合要求,但有些不符合要求(淦),看了原理直接手撕算了。
实际原理就是gradeview(iOS里的collectionView),里面添数据而已,就是处理数据比较麻烦,幸好没叫我做滑动。
说这么多,上核心代码
因为部分是项目的代码,我不可能公开,你需要的话可以私信我,女的请私信你的qq(感觉不可能)
工具类
这些函数的dateTime date 都是该月的第一天,其他不管用(主要是自己懒得再重新处理数据)
static int computeFirstDay(
DateTime date, MaterialLocalizations localizations) {
final int weekdayFromMonday = date.weekday;
final int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex + 1;
final int firstDayOfWeekFromMonday = firstDayOfWeekFromSunday % 7;
return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
}
static const List<int> _daysInMonth = <int>[
31,
-1,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
];
static int getDayCountInMonth(DateTime date) {
int month = date.month;
int year = date.year;
if (month == DateTime.february) {
final bool isLeapYear =
(year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
if (isLeapYear) return 29;
return 28;
}
return _daysInMonth[month-1];
}
这些大概是定位该月gradeView(collectionView)的显示数据位置。
UI方面的代码我不怎么想给,毕竟产品+UI都不一样
调用
List<DateModel> list = [];
List<DateTime> returnList = [];
List<TotalDateModel> totalList = [];
bool isfullChoice = false;
bool canChooseMoon = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
setState(() {
list = getDateModel(MaterialLocalizations.of(context));
});
});
}
class DateModel {
int day;
bool isChoosen;
DateModel({
required this.day,
required this.isChoosen,
});
}
创建原始数据
List<DateModel> getDateModel(MaterialLocalizations localizations){
final int currentMonthTotalDays = CommonUtils.getMonthDay(widget.date);
final int firstDayIsWeekInMonth = CommonUtils.computeFirstDay(widget.date, localizations);
final List<DateModel> dayList = [];
for(int i = 0;i < firstDayIsWeekInMonth; i++) {
final DateModel model = DateModel(day: 0,isChoosen: false);
dayList.add(model);
}
for(int i = 1; i <= currentMonthTotalDays; i ++) {
final DateModel model = DateModel(day: i,isChoosen: false);
dayList.add(model);
}
return dayList;
}
切换月份
void changeMonth(int isRight) {
totalList.firstWhere((element) => element.date == widget.date, orElse: () {
final TotalDateModel newModel = TotalDateModel(date: widget.date, dateList: list);
totalList.add(newModel);
return newModel;
}).dateList = list;
final DateTime nextDate = DateTime(widget.date.year, widget.date.month + isRight, widget.date.day);
widget.date = nextDate;
if(widget.goNextMonth != null) {
widget.goNextMonth(widget.date);
}
list = totalList.firstWhere((element) => element.date == nextDate, orElse: () {
list = getDateModel(MaterialLocalizations.of(context));
final TotalDateModel newModel = TotalDateModel(dateList: list);
return newModel;
}).dateList;
final int firstDayIsWeekInMonth =
CommonUtils.computeFirstDay(widget.date, MaterialLocalizations.of(context));
isfullChoice = true;
canChooseMoon = true;
for (int j = firstDayIsWeekInMonth; j < list.length; j++) {
final int index = list[j].day + firstDayIsWeekInMonth;
if(index%7 != 0 && (index + 1)%7 != 0){
if (list[j].isChoosen == false) {
final DateTime dateTime = DateTime(widget.date.year,widget.date.month,list[j].day);
final epochTime = dateTime.millisecondsSinceEpoch;
if ((widget.model.validStartDate ?? 0) <= epochTime && epochTime <= (widget.model.validEndDate ?? 0)){
isfullChoice = false;
break;
}
}
}
}
final int startTime = DateTime(widget.date.year,widget.date.month,1).millisecondsSinceEpoch;
final int endTime = DateTime(widget.date.year,widget.date.month,list[list.length - 1].day).millisecondsSinceEpoch;
if((widget.model.validStartDate ?? 0) > endTime || (widget.model.validEndDate ?? 0) < startTime) {
canChooseMoon = false;
isfullChoice = false;
}
setState(() {});
}
全选按钮
void chooseAllAction() {
final int firstDayIsWeekInMonth = CommonUtils.computeFirstDay(widget.date, MaterialLocalizations.of(context));
if(isfullChoice) {
for(int i = firstDayIsWeekInMonth;i < list.length;i++){
if (list[i].isChoosen) {
final int index = list[i].day + firstDayIsWeekInMonth;
if(index%7 != 0 && (index + 1)%7 != 0){
final DateTime dateTime = DateTime(widget.date.year,widget.date.month,list[i].day);
final epochTime = dateTime.millisecondsSinceEpoch;
if ((widget.model.validStartDate ?? 0) <= epochTime && epochTime <= (widget.model.validEndDate ?? 0)){
list[i].isChoosen = false;
returnList.remove(dateTime);
}
}
}
}
}else {
for(int i = firstDayIsWeekInMonth;i < list.length;i++){
if (!list[i].isChoosen) {
final int index = list[i].day + firstDayIsWeekInMonth;
if(index%7 != 0 && (index + 1)%7 != 0){
final DateTime dateTime = DateTime(widget.date.year,widget.date.month,list[i].day);
final epochTime = dateTime.millisecondsSinceEpoch;
if ((widget.model.validStartDate ?? 0) <= epochTime &&
epochTime <= (widget.model.validEndDate ?? 0)){
if (list[i].isChoosen == false) {
returnList.add(dateTime);
}
list[i].isChoosen = true;
}
}
}
}
}
setState(() {
isfullChoice = !isfullChoice;
});
}
点击单个item的处理事件
onTap: (){
final int firstDayIsWeekInMonth = CommonUtils.computeFirstDay(widget.date, MaterialLocalizations.of(context));
final DateTime dateTime = DateTime(widget.date.year,widget.date.month,list[i].day);
final epochTime = dateTime.millisecondsSinceEpoch;
if ((widget.model.validStartDate ?? 0) <= epochTime && epochTime <= (widget.model.validEndDate ?? 0)){
list[i].isChoosen = !list[i].isChoosen;
if(list[i].isChoosen){
isfullChoice = true;
returnList.add(dateTime);
for(int j = firstDayIsWeekInMonth;j < list.length;j++){
if(list[j].isChoosen == false) {
final int index = list[j].day + firstDayIsWeekInMonth;
if(index%7 != 0 && (index + 1)%7 != 0){
isfullChoice = false;
break;
}
}
}
}else {
final int index = list[i].day + firstDayIsWeekInMonth;
returnList.remove(dateTime);
if(index%7 != 0 && (index + 1)%7 != 0){
isfullChoice = false;
}
}
}
次要的判断高度
double getBottomSheetHeight(DateTime date) {
final int currentMonthTotalDays = CommonUtils.getMonthDay(date);
final int firstDayIsWeekInMonth = CommonUtils.computeFirstDay(date, MaterialLocalizations.of(context));
final int line = ((currentMonthTotalDays + firstDayIsWeekInMonth)%7).toInt() == 0 ? 0 : 1;
final lines = ((currentMonthTotalDays + firstDayIsWeekInMonth)/7).toInt();
int column = lines + line ;
final itemHeight = MediaQuery.of(context).size.width/7 ;
return column * itemHeight + 15;
}
|