介绍
最近自己写了个demo,用到了日历方面的东西,然后实现来一下,最后打算封装一下,以后可以直接拿来使用。
参考了Android的一个开源日历库,实现思路其实差不多,都是可以利用canvas将日历给画出来。
Flutter日历的项目地址:
示例
Flutter上的一个日历控件,可以定制成自己想要的样子。
主要功能
- 支持公历,农历,节气,传统节日,常用节假日
- 日期范围设置,默认支持的最大日期范围为1971.01-2055.12
- 禁用日期范围设置,比如想实现某范围的日期内可以点击,范围外的日期置灰
- 支持单选、多选模式,提供多选超过限制个数的回调和多选超过指定范围的回调。
- 跳转到指定日期,默认支持动画切换
- 自定义日历Item,支持组合widget的方式和利用canvas绘制的方式
- 自定义顶部的WeekBar
- 可以给Item添加自定义的额外数据,实现各种额外的功能。比如实现进度条风格的日历
使用
在pubspec.yaml添加依赖:
flutter_custom_calendar: git: url: https://github.com/LXD312569496/flutter_custom_calendar.git复制代码
引入flutter_custom_calendar,就可以使用CalendarViewWidget,配置CalendarController就可以了。
import 'package:flutter_custom_calendar/flutter_custom_calendar.dart';CalendarViewWidget({@required this.calendarController, this.boxDecoration});复制代码
- boxDecoration用来配置整体的背景
- 利用CalendarController来配置一些数据,并且可以通过CalendarController进行一些操作或者事件监听,比如滚动到下一个月,获取当前被选中的Item等等。
下面是CalendarController中一些支持自定义配置的属性。不配置的话,会有对应的默认值。
//默认是单选,可以配置为MODE_SINGLE_SELECT,MODE_MULTI_SELECTint 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; //注意:不能超过对应月份的总天数SetselectedDateList = new Set(); //被选中的日期,用于多选DateModel selectDateModel; //当前选择项,用于单选int maxMultiSelectCount; //多选,最多选多少个Map extraDataMap = new Map(); //自定义额外的数据//各种事件回调OnMonthChange monthChange; //月份切换事件OnCalendarSelect calendarSelect; //点击选择事件OnMultiSelectOutOfRange multiSelectOutOfRange; //多选超出指定范围OnMultiSelectOutOfSize multiSelectOutOfSize; //多选超出限制个数//支持自定义绘制DayWidgetBuilder dayWidgetBuilder; //创建日历itemWeekBarItemWidgetBuilder weekBarItemWidgetBuilder; //创建顶部的weekbar//构造函数 CalendarController( {int selectMode = Constants.MODE_SINGLE_SELECT, DayWidgetBuilder dayWidgetBuilder = defaultCustomDayWidget, WeekBarItemWidgetBuilder weekBarItemWidgetBuilder = defaultWeekBarWidget, int minYear = 1971, int maxYear = 2055, int minYearMonth = 1, int maxYearMonth = 12, int nowYear = -1, int nowMonth = -1, int minSelectYear = 1971, int minSelectMonth = 1, int minSelectDay = 1, int maxSelectYear = 2055, int maxSelectMonth = 12, int maxSelectDay = 30, Set selectedDateTimeList = EMPTY_SET, DateModel selectDateModel, int maxMultiSelectCount = 9999, Map extraDataMap = EMPTY_MAP})复制代码
利用controller添加监听事件
比如月份切换事件、点击选择事件。
//月份切换监听void addMonthChangeListener(OnMonthChange listener) { this.monthChange = listener;}//点击选择监听void addOnCalendarSelectListener(OnCalendarSelect listener) { this.calendarSelect = listener;}//多选超出指定范围void addOnMultiSelectOutOfRangeListener(OnMultiSelectOutOfRange listener) { this.multiSelectOutOfRange = listener;}//多选超出限制个数void addOnMultiSelectOutOfSizeListener(OnMultiSelectOutOfSize listener) { this.multiSelectOutOfSize = listener;}复制代码
利用controller来控制日历的切换,支持配置动画
//跳转到指定日期void moveToCalendar(int year, int month, int day, {bool needAnimation = false, Duration duration = const Duration(milliseconds: 500), Curve curve = Curves.ease});//切换到下一年void moveToNextYear();//切换到上一年void moveToPreviousYear();//切换到下一个月份,void moveToNextMonth();//切换到上一个月份void moveToPreviousMonth();复制代码
利用controller来获取日历的一些数据信息
// 获取当前的月份DateTime getCurrentMonth();//获取被选中的日期,多选SetgetMultiSelectCalendar();//获取被选中的日期,单选DateModel getSingleSelectCalendar();复制代码
自定义UI
包括自定义WeekBar、自定义日历Item,默认使用的都是DefaultXXXWidget。
只要继承对应的Base类,实现相应的方法,然后只需要在配置Controller的时候,实现相应的Builder方法就可以了。
//支持自定义绘制DayWidgetBuilder dayWidgetBuilder; //创建日历itemWeekBarItemWidgetBuilder weekBarItemWidgetBuilder; //创建顶部的weekbar复制代码
自定义WeekBar
继承BaseWeekBar,重写getWeekBarItem(index)方法就可以。随便你怎么实现,只需要返回一个Widget就可以了。
class DefaultWeekBar extends BaseWeekBar { const DefaultWeekBar({Key key}) : super(key: key); @override Widget getWeekBarItem(int index) { /** * 自定义Widget */ return new Container( height: 40, alignment: Alignment.center, child: new Text( Constants.WEEK_LIST[index], style: topWeekTextStyle, ), ); }}复制代码
自定义日历Item:
提供两种方法,一种是利用组合widget的方式来创建,一种是利用Canvas来自定义绘制Item。最后只需要在CalendarController的构造参数中进行配置就可以了。
- 继承BaseCombineDayWidget,重写getNormalWidget(DateModel dateModel) 和getSelectedWidget(DateModel dateModel)就可以了,返回对应的widget就行。
class DefaultCombineDayWidget extends BaseCombineDayWidget { DefaultCombineDayWidget(DateModel dateModel) : super(dateModel); @override Widget getNormalWidget(DateModel dateModel) { //实现默认状态下的UI } @override Widget getSelectedWidget(DateModel dateModel) { //绘制被选中的UI }}复制代码
- 继承BaseCustomDayWidget,重写drawNormal和drawSelected的两个方法就可以了,利用canvas自己绘制Item。
class DefaultCustomDayWidget extends BaseCustomDayWidget { DefaultCustomDayWidget(DateModel dateModel) : super(dateModel); @override void drawNormal(DateModel dateModel, Canvas canvas, Size size) { //实现默认状态下的UI defaultDrawNormal(dateModel, canvas, size); } @override void drawSelected(DateModel dateModel, Canvas canvas, Size size) { //绘制被选中的UI defaultDrawSelected(dateModel, canvas, size); }}复制代码
DateModel实体类
日历所用的日期的实体类DateModel,有下面这些属性。
/** * 日期的实体类 */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); } //根据DateTime创建对应的model,并初始化农历和传统节日等信息 static DateModel fromDateTime(DateTime dateTime) { DateModel dateModel = new DateModel() ..year = dateTime.year ..month = dateTime.month ..day = dateTime.day; LunarUtil.setupLunarCalendar(dateModel); return dateModel; } @override bool operator ==(Object other) => identical(this, other) || other is DateModel && runtimeType == other.runtimeType && year == other.year && month == other.month && day == other.day; @override int get hashCode => year.hashCode ^ month.hashCode ^ day.hashCode;}复制代码
TODO LIST
- 优化代码实现
- 支持屏蔽指定的某些天
- 继续写几个不同风格的Demo
- 支持周视图
- 支持动画切换周视图和月视图