创建configuration类,将配置的信息放到这里

引入provider状态管理,避免深层嵌套传递信息
周视图和月视图,联动
增加日志输出类LogUtil,方便查看调试
增加example例子
性能优化
develop
xiaodong 5 years ago
parent 93009e9324
commit b26999d210

@ -1,3 +1,11 @@
## [1.0.0] - 2019/9/22
* 重构日历的代码
* 创建configuration类将配置的信息放到这里
* 引入provider状态管理,避免深层嵌套传递信息
* 实现周视图,并实现周视图和月视图之间的联动
* DateModel增加isCurrentMonth用于绘制月视图可以屏蔽一些非当前月份的日子前面几天或者后面几天的isCurrentMonth是为false的。
## [0.0.1] - 2019/5/19. ## [0.0.1] - 2019/5/19.
## 主要功能 ## 主要功能

@ -7,6 +7,27 @@ Flutter上的一个日历控件可以定制成自己想要的样子。
## 主要功能 ## 主要功能
* 支持公历,农历,节气,传统节日,常用节假日
* 日期范围设置默认支持的最大日期范围为1971.01-2055.12
* 禁用日期范围设置,比如想实现某范围的日期内可以点击,范围外的日期置灰
* 支持单选、多选模式,提供多选超过限制个数的回调和多选超过指定范围的回调。
* 跳转到指定日期,默认支持动画切换
* 自定义日历Item支持组合widget的方式和利用canvas绘制的方式
* 自定义顶部的WeekBar
* 根据实际场景可以给Item添加自定义的额外数据实现各种额外的功能。比如实现进度条风格的日历实现日历的各种标记
* 支持周视图的展示
* 支持月份视图和星期视图的展示与切换联动
## 近期修改
### [1.0.0] - 2019/9/22
* 重构日历的代码
* 创建configuration类将配置的信息放到这里
* 引入provider状态管理,避免深层嵌套传递信息
* 实现周视图,并实现周视图和月视图之间的联动
* DateModel增加isCurrentMonth用于绘制月视图可以屏蔽一些非当前月份的日子前面几天或者后面几天的isCurrentMonth是为false的。
### [0.0.1] - 2019/5/19.
* 支持公历,农历,节气,传统节日,常用节假日 * 支持公历,农历,节气,传统节日,常用节假日
* 日期范围设置默认支持的最大日期范围为1971.01-2055.12 * 日期范围设置默认支持的最大日期范围为1971.01-2055.12
* 禁用日期范围设置,比如想实现某范围的日期内可以点击,范围外的日期置灰 * 禁用日期范围设置,比如想实现某范围的日期内可以点击,范围外的日期置灰
@ -30,50 +51,19 @@ import 'package:flutter_custom_calendar/flutter_custom_calendar.dart';
CalendarViewWidget({@required this.calendarController, this.boxDecoration}); CalendarViewWidget({@required this.calendarController, this.boxDecoration});
``` ```
* boxDecoration用来配置整体的背景 * boxDecoration用来配置整体的背景
* 利用CalendarController来配置一些数据并且可以通过CalendarController进行一些操作或者事件监听比如滚动到下一个月获取当前被选中的Item等等。 * 利用CalendarController来配置一些数据并且可以通过CalendarController进行一些操作或者事件监听比如滚动到下一个月获取当前被选中的Item等等。
下面是CalendarController中一些支持自定义配置的属性。不配置的话会有对应的默认值。 下面是CalendarController中一些支持自定义配置的属性。不配置的话会有对应的默认值。
``` 配置都是在controller里面进行配置的。。考虑到之前版本所以才这样搞
//默认是单选,可以配置为MODE_SINGLE_SELECTMODE_MULTI_SELECT
int selectMode;
//日历显示的最小年份和最大年份
int minYear;
int maxYear;
//日历显示的最小年份的月份,最大年份的月份
int minYearMonth;
int maxYearMonth;
//日历显示的当前的年份和月份
int nowYear;
int nowMonth;
//可操作的范围设置,比如点击选择
int minSelectYear;
int minSelectMonth;
int minSelectDay;
int maxSelectYear;
int maxSelectMonth;
int maxSelectDay; //注意:不能超过对应月份的总天数
Set<DateModel> selectedDateList = new Set(); //被选中的日期,用于多选
DateModel selectDateModel; //当前选择项,用于单选
int maxMultiSelectCount; //多选,最多选多少个
Map<DateTime, Object> extraDataMap = new Map(); //自定义额外的数据
//各种事件回调 个人觉得配置的含义主要包括了3个方面的配置。
OnMonthChange monthChange; //月份切换事件 * 一个是显示日历所需要的相关数据,
OnCalendarSelect calendarSelect; //点击选择事件 * 一个是显示日历的自定义UI的相关配置
OnMultiSelectOutOfRange multiSelectOutOfRange; //多选超出指定范围 * 一个是对日历的监听事件进行配置。
OnMultiSelectOutOfSize multiSelectOutOfSize; //多选超出限制个数
//支持自定义绘制
DayWidgetBuilder dayWidgetBuilder; //创建日历item
WeekBarItemWidgetBuilder weekBarItemWidgetBuilder; //创建顶部的weekbar
```
//构造函数 //构造函数
CalendarController( CalendarController(
{int selectMode = Constants.MODE_SINGLE_SELECT, {int selectMode = Constants.MODE_SINGLE_SELECT,
@ -98,56 +88,71 @@ WeekBarItemWidgetBuilder weekBarItemWidgetBuilder; //创建顶部的weekbar
``` ```
### 利用controller添加监听事件 数据方面的配置
比如月份切换事件、点击选择事件。
``` 属性 | 含义 | 默认值
//月份切换监听 :-: | :-: | :-:
void addMonthChangeListener(OnMonthChange listener) { selectMode | 选择模式,表示单选或者多选 | 默认是单选MODE_SINGLE_SELECT
this.monthChange = listener; minYear | 日历显示的最小年份| 1971
} maxYear | 日历显示的最大年份| 2055
//点击选择监听 minYearMonth | 日历显示的最小年份的月份| 1
void addOnCalendarSelectListener(OnCalendarSelect listener) { maxYearMonth | 日历显示的最大年份的月份| 12
this.calendarSelect = listener; nowYear | 日历显示的当前的年份| -1
} nowMonth | 日历显示的当前的月份| -1
//多选超出指定范围 minSelectYear | 可以选择的最小年份| 1971
void addOnMultiSelectOutOfRangeListener(OnMultiSelectOutOfRange listener) { minSelectMonth | 可以选择的最小年份的月份| 1
this.multiSelectOutOfRange = listener; minSelectDay | 可以选择的最小月份的日子| 1
} maxSelectYear | 可以选择的最大年份| 2055
//多选超出限制个数 maxSelectMonth | 可以选择的最大年份的月份| 12
void addOnMultiSelectOutOfSizeListener(OnMultiSelectOutOfSize listener) { maxSelectDay | 可以选择的最大月份的日子| 30注意不能超过对应月份的总天数
this.multiSelectOutOfSize = listener; selectedDateList | 被选中的日期,用于多选| 默认为空Set, Set<DateModel> selectedDateList = new Set()
} selectDateModel | 当前选择项,用于单选| 默认为空
``` maxMultiSelectCount | 多选,最多选多少个| hhh
extraDataMap | 自定义额外的数据| 默认为空MapMap<DateTime, Object> extraDataMap = new Map()
UI绘制相关的配置
属性 | 含义 | 默认值
:-: | :-: | :-:
weekBarItemWidgetBuilder | 创建顶部的weekbar | 默认样式
dayWidgetBuilder | 创建日历item | 默认样式
事件监听的配置
方法 | 含义 | 默认值
:-: | :-: | :-:
void addMonthChangeListener(OnMonthChange listener) | 月份切换事件 | 默认为空
void addOnCalendarSelectListener(OnCalendarSelect listener) | 点击选择事件 | 默认为空
void addOnMultiSelectOutOfRangeListener(OnMultiSelectOutOfRange listener) | 多选超出指定范围 | 默认为空
void addOnMultiSelectOutOfSizeListener(OnMultiSelectOutOfSize listener) | 多选超出限制个数 | 默认为空
void addExpandChangeListener(ValueChanged<bool> expandChange)|监听日历的展开收缩状态|
### 利用controller来控制日历的切换支持配置动画 ### 利用controller来控制日历的切换支持配置动画
``` 方法 | 含义 | 默认值
//跳转到指定日期 :-: | :-: | :-:
void moveToCalendar(int year, int month, int day, Future<bool> previousPage()|滑动到上一个页面会自动根据当前的展开状态滑动到上一个月或者上一个星期。如果已经在第一个页面没有上一个页面就会返回false其他情况返回true|
{bool needAnimation = false, Future<bool> nextPage()|滑动到下一个页面会自动根据当前的展开状态滑动到下一个月或者下一个星期。如果已经在最后一个页面没有下一个页面就会返回false其他情况返回true|
Duration duration = const Duration(milliseconds: 500), void moveToCalendar(int year, int month, int day, {bool needAnimation = false,Duration duration = const Duration(milliseconds: 500),Curve curve = Curves.ease}) | 到指定日期 | 默认为空
Curve curve = Curves.ease}); void moveToNextYear()|切换到下一年|
//切换到下一年 void moveToPreviousYear()|切换到上一年|
void moveToNextYear(); void moveToNextMonth()|切换到下一个月份|
//切换到上一年 void moveToPreviousMonth()|切换到上一个月份|
void moveToPreviousYear(); void toggleExpandStatus()|切换展开状态|
//切换到下一个月份,
void moveToNextMonth();
//切换到上一个月份
void moveToPreviousMonth();
```
### 利用controller来获取日历的一些数据信息 ### 利用controller来获取日历的一些数据信息
```
// 获取当前的月份
DateTime getCurrentMonth();
//获取被选中的日期,多选
Set<DateModel> getMultiSelectCalendar();
//获取被选中的日期,单选
DateModel getSingleSelectCalendar();
```
### 自定义UI 方法 | 含义 | 默认值
:-: | :-: | :-:
DateTime getCurrentMonth()|获取当前的月份|
Set<DateModel> getMultiSelectCalendar()|获取被选中的日期,多选|
DateModel getSingleSelectCalendar()|获取被选中的日期,单选|
### 如何自定义UI
包括自定义WeekBar、自定义日历Item默认使用的都是DefaultXXXWidget。 包括自定义WeekBar、自定义日历Item默认使用的都是DefaultXXXWidget。
@ -197,7 +202,10 @@ class DefaultCombineDayWidget extends BaseCombineDayWidget {
} }
} }
``` ```
* 继承BaseCustomDayWidget重写drawNormal和drawSelected的两个方法就可以了利用canvas自己绘制Item。 * 继承BaseCustomDayWidget重写drawNormal和drawSelected的两个方法就可以了利用canvas自己绘制Item。
``` ```
class DefaultCustomDayWidget extends BaseCustomDayWidget { class DefaultCustomDayWidget extends BaseCustomDayWidget {
DefaultCustomDayWidget(DateModel dateModel) : super(dateModel); DefaultCustomDayWidget(DateModel dateModel) : super(dateModel);
@ -213,64 +221,88 @@ class DefaultCustomDayWidget extends BaseCustomDayWidget {
} }
} }
``` ```
### DateModel实体类
日历所用的日期的实体类DateModel有下面这些属性。 ### 根据实际场景自定义额外的数据extraData
#### 自定义每个item的进度条数据
```
//外部处理每个dateModel所对应的进度
Map<DateModel, int> progressMap = {
DateModel.fromDateTime(temp.add(Duration(days: 1))): 0,
DateModel.fromDateTime(temp.add(Duration(days: 2))): 20,
DateModel.fromDateTime(temp.add(Duration(days: 3))): 40,
DateModel.fromDateTime(temp.add(Duration(days: 4))): 60,
DateModel.fromDateTime(temp.add(Duration(days: 5))): 80,
DateModel.fromDateTime(temp.add(Duration(days: 6))): 100,
};
//创建CalendarController对象的时候将extraDataMap赋值就行了
new CalendarController(
extraDataMap: progressMap)
//绘制DayWidget的时候可以直接从dateModel的extraData对象中拿到想要的数据
int progress = dateModel.extraData;
``` ```
/**
* 日期的实体类
*/
class DateModel {
int year;
int month;
int day = 1;
int lunarYear;
int lunarMonth;
int lunarDay;
String lunarString; //农历字符串
String solarTerm; //24节气
String gregorianFestival; //公历节日
String traditionFestival; //传统农历节日
bool isCurrentDay; //是否是今天
bool isLeapYear; //是否是闰年
bool isWeekend; //是否是周末
int leapMonth; //是否是闰月
Object extraData; //自定义的额外数据
bool isInRange = false; //是否在范围内,比如可以实现在某个范围外,设置置灰的功能
bool isSelected; //是否被选中,用来实现一些标记或者选择功能
@override
String toString() {
return 'DateModel{year: $year, month: $month, day: $day}';
} //如果是闰月,则返回闰月
//转化成DateTime格式 #### 自定义各种标记
DateTime getDateTime() { ```
return new DateTime(year, month, day); //外部处理每个dateModel所对应的标记
} Map<DateModel, String> customExtraData = {
//根据DateTime创建对应的model并初始化农历和传统节日等信息 DateModel.fromDateTime(DateTime.now().add(Duration(days: -1))): "假",
static DateModel fromDateTime(DateTime dateTime) { DateModel.fromDateTime(DateTime.now().add(Duration(days: -2))): "游",
DateModel dateModel = new DateModel() DateModel.fromDateTime(DateTime.now().add(Duration(days: -3))): "事",
..year = dateTime.year DateModel.fromDateTime(DateTime.now().add(Duration(days: -4))): "班",
..month = dateTime.month DateModel.fromDateTime(DateTime.now().add(Duration(days: -5))): "假",
..day = dateTime.day; DateModel.fromDateTime(DateTime.now().add(Duration(days: -6))): "游",
LunarUtil.setupLunarCalendar(dateModel); DateModel.fromDateTime(DateTime.now().add(Duration(days: 2))): "游",
return dateModel; DateModel.fromDateTime(DateTime.now().add(Duration(days: 3))): "事",
} DateModel.fromDateTime(DateTime.now().add(Duration(days: 4))): "班",
@override DateModel.fromDateTime(DateTime.now().add(Duration(days: 5))): "假",
bool operator ==(Object other) => DateModel.fromDateTime(DateTime.now().add(Duration(days: 6))): "游",
identical(this, other) || DateModel.fromDateTime(DateTime.now().add(Duration(days: 7))): "事",
other is DateModel && DateModel.fromDateTime(DateTime.now().add(Duration(days: 8))): "班",
runtimeType == other.runtimeType && };
year == other.year && //创建CalendarController对象的时候将extraDataMap赋值就行了
month == other.month && new CalendarController(
day == other.day; extraDataMap: customExtraData)
@override //绘制DayWidget的时候可以直接从dateModel的extraData对象中拿到想要的数据
int get hashCode => year.hashCode ^ month.hashCode ^ day.hashCode; String data = dateModel.extraData;
}
``` ```
### DateModel实体类
日历所用的日期的实体类DateModel有下面这些属性。可以在自定义绘制DayWidget的时候根据相应的属性进行判断后绘制相应的UI。
属性|含义|类型|默认值
:-: | :-: | :-: |:-:
year|年份|int|
month|月份|int|
day|日期|int|默认为1
lunarYear|农历年份|int|
lunarMonth|农历月份|int|
lunarDay|农历日期|int|
lunarString|农历字符串|String|
solarTerm|24节气|String|
gregorianFestival|gregorianFestival|String|
traditionFestival|传统农历节日|String|
isCurrentDay|是否是今天|bool|
isLeapYear|是否是闰年|bool|
isWeekend|是否是周末|bool|
isInRange|是否在范围内,比如可以实现在某个范围外,设置置灰的功能|bool|false
isSelected|是否被选中,用来实现一些标记或者选择功能|bool|false
extraData|自定义的额外数据|Object
方法|含义|
:-: | :-: |
DateTime getDateTime()|将DateModel转化成DateTime
DateModel fromDateTime(DateTime dateTime)|根据DateTime创建对应的model并初始化农历和传统节日等信息
bool operator ==(Object other)|重写==方法可以判断两个dateModel是否是同一天
## TODO LIST ## TODO LIST
* 优化代码实现 * 优化代码实现
* 支持屏蔽指定的某些天
* 继续写几个不同风格的Demo * 继续写几个不同风格的Demo
* 支持周视图 * 支持手势操作
* 支持动画切换周视图和月视图

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_custom_calendar/configuration.dart'; import 'package:flutter_custom_calendar/configuration.dart';
import 'package:flutter_custom_calendar/model/date_model.dart'; import 'package:flutter_custom_calendar/model/date_model.dart';
import 'package:flutter_custom_calendar/utils/LogUtil.dart'; import 'package:flutter_custom_calendar/utils/LogUtil.dart';
import 'package:flutter_custom_calendar/widget/month_view.dart';
/** /**
* provider * provider
@ -12,6 +13,7 @@ class CalendarProvider extends ChangeNotifier {
Set<DateModel> selectedDateList = new Set(); //, Set<DateModel> selectedDateList = new Set(); //,
DateModel _selectDateModel; // DateModel _selectDateModel; //
DateModel lastClickDateModel; // DateModel lastClickDateModel; //
MultiSelectItemContainerState lastClickItemState;
DateModel get selectDateModel => _selectDateModel; DateModel get selectDateModel => _selectDateModel;

@ -70,12 +70,16 @@ class CalendarContainerState extends State<CalendarContainer>
void initState() { void initState() {
calendarProvider = Provider.of<CalendarProvider>(context, listen: false); calendarProvider = Provider.of<CalendarProvider>(context, listen: false);
expand = calendarProvider.expandStatus.value; expand = calendarProvider.expandStatus.value;
//
if (calendarProvider.calendarConfiguration.enableExpand == true) {
calendarProvider.expandStatus.addListener(() { calendarProvider.expandStatus.addListener(() {
setState(() { setState(() {
expand = !expand; expand = !expand;
}); });
}); });
} }
}
@override @override
void dispose() { void dispose() {

@ -65,8 +65,8 @@ class _MonthViewState extends State<MonthView> {
} }
Widget getView() { Widget getView() {
return Consumer<CalendarProvider>( CalendarProvider calendarProvider =
builder: (context, calendarProvider, child) { Provider.of<CalendarProvider>(context, listen: false);
CalendarConfiguration configuration = CalendarConfiguration configuration =
calendarProvider.calendarConfiguration; calendarProvider.calendarConfiguration;
@ -92,7 +92,52 @@ class _MonthViewState extends State<MonthView> {
} }
} }
return GestureDetector( return MultiSelectItemContainer(
dateModel: dateModel,
configuration: configuration,
calendarProvider: calendarProvider,
);
});
}
}
/**
* itemitem
*/
class MultiSelectItemContainer extends StatefulWidget {
final DateModel dateModel;
CalendarConfiguration configuration;
CalendarProvider calendarProvider;
MultiSelectItemContainer(
{Key key, this.dateModel, this.configuration, this.calendarProvider})
: super(key: key);
@override
MultiSelectItemContainerState createState() =>
MultiSelectItemContainerState();
}
class MultiSelectItemContainerState extends State<MultiSelectItemContainer> {
DateModel dateModel;
CalendarConfiguration configuration;
CalendarProvider calendarProvider;
@override
void initState() {
super.initState();
dateModel = widget.dateModel;
configuration = widget.configuration;
calendarProvider = widget.calendarProvider;
}
@override
Widget build(BuildContext context) {
// LogUtil.log(
// TAG: this.runtimeType,
// message: "_ItemContainerState build ${dateModel}");
return Container(
child: GestureDetector(
//item //item
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () { onTap: () {
@ -131,11 +176,32 @@ class _MonthViewState extends State<MonthView> {
} else { } else {
calendarProvider.selectDateModel = dateModel; calendarProvider.selectDateModel = dateModel;
configuration.calendarSelect(dateModel); configuration.calendarSelect(dateModel);
calendarProvider.lastClickItemState?.refreshItem();
calendarProvider.lastClickItemState = this;
} }
refreshItem();
}, },
child: configuration.dayWidgetBuilder(dateModel), child: configuration.dayWidgetBuilder(dateModel),
),
); );
}
/**
* item
*/
void refreshItem() {
/**
*
Exception caught by gesture
The following assertion was thrown while handling a gesture:
setState() called after dispose()
*/
if(mounted){
setState(() {
dateModel.isSelected = !dateModel.isSelected;
}); });
}); }
} }
} }

@ -1,5 +1,5 @@
# Generated by pub # Generated by pub
# See https://www.dartlang.org/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
async: async:
dependency: transitive dependency: transitive
@ -7,14 +7,14 @@ packages:
name: async name: async
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.1.0" version: "2.3.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.0.4" version: "1.0.5"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -52,7 +52,7 @@ packages:
name: meta name: meta
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.1.6" version: "1.1.7"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -66,14 +66,21 @@ packages:
name: pedantic name: pedantic
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.5.0" version: "1.8.0+1"
provider:
dependency: "direct main"
description:
name: provider
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.0+1"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:
name: quiver name: quiver
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.0.2" version: "2.0.3"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -120,7 +127,7 @@ packages:
name: test_api name: test_api
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.2.4" version: "0.2.5"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -136,4 +143,4 @@ packages:
source: hosted source: hosted
version: "2.0.8" version: "2.0.8"
sdks: sdks:
dart: ">=2.2.0 <3.0.0" dart: ">=2.2.2 <3.0.0"

Loading…
Cancel
Save