You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

330 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_custom_calendar/cache_data.dart';
import 'package:flutter_custom_calendar/calendar_provider.dart';
import 'package:flutter_custom_calendar/configuration.dart';
import 'package:flutter_custom_calendar/constants/constants.dart';
import 'package:flutter_custom_calendar/model/date_model.dart';
import 'package:flutter_custom_calendar/utils/LogUtil.dart';
import 'package:flutter_custom_calendar/utils/date_util.dart';
import 'package:provider/provider.dart';
/**
* 月视图,显示整个月的日子
*/
class MonthView extends StatefulWidget {
final int year;
final int month;
final int day;
final CalendarConfiguration configuration;
const MonthView({
Key key,
@required this.year,
@required this.month,
this.day,
this.configuration,
}) : super(key: key);
@override
_MonthViewState createState() => _MonthViewState();
}
class _MonthViewState extends State<MonthView>
with AutomaticKeepAliveClientMixin {
List<DateModel> items = List();
int lineCount;
Map<DateModel, Object> extraDataMap; //自定义额外的数据
@override
void initState() {
super.initState();
extraDataMap = widget.configuration.extraDataMap;
DateModel firstDayOfMonth =
DateModel.fromDateTime(DateTime(widget.year, widget.month, 1));
if (CacheData.getInstance().monthListCache[firstDayOfMonth]?.isNotEmpty ==
true) {
LogUtil.log(TAG: this.runtimeType, message: "缓存中有数据");
items = CacheData.getInstance().monthListCache[firstDayOfMonth];
} else {
LogUtil.log(TAG: this.runtimeType, message: "缓存中无数据");
getItems().then((_) {
CacheData.getInstance().monthListCache[firstDayOfMonth] = items;
});
}
lineCount = DateUtil.getMonthViewLineCount(
widget.year, widget.month, widget.configuration.offset);
//第一帧后,添加监听generation发生变化后需要刷新整个日历
WidgetsBinding.instance.addPostFrameCallback((callback) {
Provider.of<CalendarProvider>(context, listen: false)
.generation
.addListener(() async {
extraDataMap = widget.configuration.extraDataMap;
await getItems();
});
});
}
Future getItems() async {
items = await compute(initCalendarForMonthView, {
'year': widget.year,
'month': widget.month,
'minSelectDate': widget.configuration.minSelectDate,
'maxSelectDate': widget.configuration.maxSelectDate,
'extraDataMap': extraDataMap,
'offset': widget.configuration.offset
});
setState(() {});
}
static Future<List<DateModel>> initCalendarForMonthView(Map map) async {
return DateUtil.initCalendarForMonthView(
map['year'], map['month'], DateTime.now(), DateTime.sunday,
minSelectDate: map['minSelectDate'],
maxSelectDate: map['maxSelectDate'],
extraDataMap: map['extraDataMap'],
offset: map['offset']);
}
@override
Widget build(BuildContext context) {
super.build(context);
LogUtil.log(TAG: this.runtimeType, message: "_MonthViewState build");
CalendarProvider calendarProvider =
Provider.of<CalendarProvider>(context, listen: false);
CalendarConfiguration configuration =
calendarProvider.calendarConfiguration;
return new GridView.builder(
addAutomaticKeepAlives: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7, mainAxisSpacing: configuration.verticalSpacing),
itemCount: items.isEmpty ? 0 : items.length,
itemBuilder: (context, index) {
DateModel dateModel = items[index];
//判断是否被选择
switch (configuration.selectMode) {
/// 多选
case CalendarConstants.MODE_MULTI_SELECT:
if (calendarProvider.selectedDateList.contains(dateModel)) {
dateModel.isSelected = true;
} else {
dateModel.isSelected = false;
}
break;
/// 选择开始和结束 中间的自动选择
case CalendarConstants.MODE_MULTI_SELECT_FROM_TO_END:
if (calendarProvider.selectedDateList.contains(dateModel)) {
dateModel.isSelected = true;
} else {
dateModel.isSelected = false;
}
break;
/// 单选
case CalendarConstants.MODE_SINGLE_SELECT:
if (calendarProvider.selectDateModel == dateModel) {
dateModel.isSelected = true;
} else {
dateModel.isSelected = false;
}
break;
}
return ItemContainer(
dateModel: dateModel,
key: ObjectKey(
dateModel), //这里使用objectKey保证可以刷新。原因1跟flutter的刷新机制有关。原因2statefulElement持有state。
);
});
}
@override
bool get wantKeepAlive => true;
}
/**
* 多选模式包装item这样的话就只需要刷新当前点击的item就行了不需要刷新整个页面
*/
class ItemContainer extends StatefulWidget {
final DateModel dateModel;
const ItemContainer({
Key key,
this.dateModel,
}) : super(key: key);
@override
ItemContainerState createState() => ItemContainerState();
}
class ItemContainerState extends State<ItemContainer> {
DateModel dateModel;
CalendarConfiguration configuration;
CalendarProvider calendarProvider;
ValueNotifier<bool> isSelected;
@override
void initState() {
super.initState();
dateModel = widget.dateModel;
isSelected = ValueNotifier(dateModel.isSelected);
// 先注释掉这段代码
// WidgetsBinding.instance.addPostFrameCallback((callback) {
// if (configuration.selectMode == CalendarConstants.MODE_SINGLE_SELECT &&
// dateModel.isSelected) {
// calendarProvider.lastClickItemState = this;
// }
// });
}
/**
* 提供方法给外部可以调用这个方法进行刷新item
*/
void refreshItem(bool v) {
/**
Exception caught by gesture
The following assertion was thrown while handling a gesture:
setState() called after dispose()
*/
if (mounted) {
setState(() {
dateModel.isSelected = v;
// isSelected.value = !isSelected.value;
});
}
}
@override
Widget build(BuildContext context) {
// LogUtil.log(TAG: this.runtimeType, message: "ItemContainerState build");
calendarProvider = Provider.of<CalendarProvider>(context, listen: false);
configuration = calendarProvider.calendarConfiguration;
return GestureDetector(
//点击整个item都会触发事件
behavior: HitTestBehavior.opaque,
onTap: () {
LogUtil.log(
TAG: this.runtimeType,
message: "GestureDetector onTap: $dateModel}");
//范围外不可点击
if (!dateModel.isInRange) {
//多选回调
if (configuration.selectMode == CalendarConstants.MODE_MULTI_SELECT) {
configuration.multiSelectOutOfRange();
}
return;
}
calendarProvider.lastClickDateModel = dateModel;
switch (configuration.selectMode) {
//简单多选
case CalendarConstants.MODE_MULTI_SELECT:
if (calendarProvider.selectedDateList.contains(dateModel)) {
calendarProvider.selectedDateList.remove(dateModel);
} else {
//多选,判断是否超过限制,超过范围
if (calendarProvider.selectedDateList.length ==
configuration.maxMultiSelectCount) {
if (configuration.multiSelectOutOfSize != null) {
configuration.multiSelectOutOfSize();
}
return;
}
calendarProvider.selectedDateList.add(dateModel);
}
if (configuration.calendarSelect != null) {
configuration.calendarSelect(dateModel);
}
//多选也可以弄这些单选的代码
calendarProvider.selectDateModel = dateModel;
break;
/// 单选
case CalendarConstants.MODE_SINGLE_SELECT:
calendarProvider.selectDateModel = dateModel;
if (configuration.calendarSelect != null) {
configuration.calendarSelect(dateModel);
}
//单选需要刷新上一个item
if (calendarProvider.lastClickItemState != this) {
calendarProvider.lastClickItemState?.refreshItem(false);
calendarProvider.lastClickItemState = this;
}
break;
/// 选择范围
case CalendarConstants.MODE_MULTI_SELECT_FROM_TO_END:
if (calendarProvider.selectedDateList.length == 0) {
calendarProvider.selectedDateList.add(dateModel);
} else if (calendarProvider.selectedDateList.length == 1) {
DateModel d2 = calendarProvider.selectedDateList.last;
DateTime t1, t2;
if (d2.getDateTime().isAfter(dateModel.getDateTime())) {
t2 = d2.getDateTime();
t1 = dateModel.getDateTime();
} else {
t1 = d2.getDateTime();
t2 = dateModel.getDateTime();
}
for (; t1.isBefore(t2);) {
calendarProvider.selectedDateList
.add(DateModel.fromDateTime(t1));
t1 = t1.add(Duration(days: 1));
}
} else {
/// 加入已经选择了多个 则进行取消操作
calendarProvider.selectedDateList.clear();
}
if (configuration.calendarSelect != null) {
calendarProvider.selectedDateList.forEach((element) {
configuration.calendarSelect(element);
});
}
break;
}
refreshItem(!this.dateModel.isSelected);
},
child: configuration.dayWidgetBuilder(dateModel),
);
}
@override
void deactivate() {
// LogUtil.log(
// TAG: this.runtimeType, message: "ItemContainerState deactivate");
super.deactivate();
}
@override
void dispose() {
// LogUtil.log(TAG: this.runtimeType, message: "ItemContainerState dispose");
super.dispose();
}
@override
void didUpdateWidget(ItemContainer oldWidget) {
// LogUtil.log(
// TAG: this.runtimeType, message: "ItemContainerState didUpdateWidget");
super.didUpdateWidget(oldWidget);
}
}