|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
//第一帧后,添加监听,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
|
|
|
|
|
});
|
|
|
|
|
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']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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 : 7 * lineCount,
|
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
|
DateModel dateModel = items[index];
|
|
|
|
|
//判断是否被选择
|
|
|
|
|
if (configuration.selectMode == CalendarConstants.MODE_MULTI_SELECT) {
|
|
|
|
|
if (calendarProvider.selectedDateList.contains(dateModel)) {
|
|
|
|
|
dateModel.isSelected = true;
|
|
|
|
|
} else {
|
|
|
|
|
dateModel.isSelected = false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (calendarProvider.selectDateModel == dateModel) {
|
|
|
|
|
dateModel.isSelected = true;
|
|
|
|
|
} else {
|
|
|
|
|
dateModel.isSelected = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ItemContainer(
|
|
|
|
|
dateModel: dateModel,
|
|
|
|
|
key: ObjectKey(
|
|
|
|
|
dateModel), //这里使用objectKey,保证可以刷新。原因1:跟flutter的刷新机制有关。原因2:statefulElement持有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;
|
|
|
|
|
|
|
|
|
|
if (configuration.selectMode == 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);
|
|
|
|
|
}
|
|
|
|
|
configuration.calendarSelect(dateModel);
|
|
|
|
|
|
|
|
|
|
//多选也可以弄这些单选的代码
|
|
|
|
|
calendarProvider.selectDateModel = dateModel;
|
|
|
|
|
} else {
|
|
|
|
|
calendarProvider.selectDateModel = dateModel;
|
|
|
|
|
configuration.calendarSelect(dateModel);
|
|
|
|
|
|
|
|
|
|
//单选需要刷新上一个item
|
|
|
|
|
if (calendarProvider.lastClickItemState != this) {
|
|
|
|
|
calendarProvider.lastClickItemState?.refreshItem(false);
|
|
|
|
|
calendarProvider.lastClickItemState = this;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|