FlutterCalendarWidget
Flutter上的一個日曆控制元件,可以定製成自己想要的樣子。Github地址
介紹
之前寫了一個Flutter日曆的開源庫,最近增加了一些功能,並且對程式碼進行了一下重構,再搞了下效能優化。(之前的程式碼寫得真的是****,沒搞狀態框架,還各種巢狀程式碼)
示例
日曆支援web預覽:點選此處進入預覽
主要功能
- 支援公曆,農曆,節氣,傳統節日,常用節假日
- 日期範圍設定,預設支援的最大日期範圍為1971.01-2055.12
- 禁用日期範圍設定,比如想實現某範圍的日期內可以點選,範圍外的日期置灰
- 支援單選、多選模式,提供多選超過限制個數的回撥和多選超過指定範圍的回撥。
- 跳轉到指定日期,預設支援動畫切換
- 自定義日曆Item,支援組合widget的方式和利用canvas繪製的方式
- 自定義頂部的WeekBar
- 根據實際場景,可以給Item新增自定義的額外資料,實現各種額外的功能。比如實現進度條風格的日曆,實現日曆的各種標記
- 支援周檢視的展示
- 支援月份檢視和星期檢視的展示與切換聯動
近期修改
[1.0.0] - 2019/10/10
- 重構日曆的程式碼,進行效能優化
- 建立configuration類,將配置的資訊放到這裡
- 引入provider狀態管理,避免資料各種巢狀傳遞,以及實現區域性重新整理
- 實現周檢視,並實現周檢視和月檢視之間的聯動。使用IndexStack包裝兩個周檢視和月檢視,切換的使用修改IndexStack的index就行了
- DateModel增加isCurrentMonth,用於繪製月檢視可以遮蔽一些非當前月份的日子,前面幾天或者後面幾天的isCurrentMonth是為false的。
- 點選item後進行重新整理,控制重新整理範圍:單選模式下只重新整理兩個item,當前item和上一個item。多選模式下只重新整理選中的item就行了。
- DateModel的資料初始化,部分屬性使用get方法進行懶載入
[0.0.1] - 2019/5/19.
- 支援公曆,農曆,節氣,傳統節日,常用節假日
- 日期範圍設定,預設支援的最大日期範圍為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
下面是CalendarController中一些支援自定義配置的屬性。不配置的話,會有對應的預設值。(配置現在都是在controller這裡進行配置的,內部會將配置的資料抽成Configuration類)
配置的含義主要包括了3個方面的配置。
- 一個是顯示日曆所需要的相關資料,
- 一個是顯示日曆的自定義UI的相關配置,
- 一個是對日曆的監聽事件進行配置。
//建構函式
CalendarController(
{int selectMode = Constants.MODE_SINGLE_SELECT,
int showMode = Constants.MODE_SHOW_ONLY_MONTH,
bool expandStatus = true,
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<DateTime> selectedDateTimeList = EMPTY_SET,
DateModel selectDateModel,
int maxMultiSelectCount = 9999,
double verticalSpacing = 10,
bool enableExpand = true,
Map<DateModel, Object> extraDataMap = EMPTY_MAP})
複製程式碼
資料方面的配置
屬性 | 含義 | 預設值 |
---|---|---|
selectMode | 選擇模式,表示單選或者多選 | 預設是單選 static const int MODE_SINGLE_SELECT = 1; static const int MODE_MULTI_SELECT = 2; |
showMode | 展示模式 | 預設是隻展示月檢視 static const int MODE_SHOW_ONLY_MONTH=1;//僅支援月檢視 static const int MODE_SHOW_ONLY_WEEK=2;//僅支援星期檢視 static const int MODE_SHOW_WEEK_AND_MONTH=3;//支援月和星期檢視切換 |
minYear | 日曆顯示的最小年份 | 1971 |
maxYear | 日曆顯示的最大年份 | 2055 |
minYearMonth | 日曆顯示的最小年份的月份 | 1 |
maxYearMonth | 日曆顯示的最大年份的月份 | 12 |
nowYear | 日曆顯示的當前的年份 | -1 |
nowMonth | 日曆顯示的當前的月份 | -1 |
minSelectYear | 可以選擇的最小年份 | 1971 |
minSelectMonth | 可以選擇的最小年份的月份 | 1 |
minSelectDay | 可以選擇的最小月份的日子 | 1 |
maxSelectYear | 可以選擇的最大年份 | 2055 |
maxSelectMonth | 可以選擇的最大年份的月份 | 12 |
maxSelectDay | 可以選擇的最大月份的日子 | 30,注意:不能超過對應月份的總天數 |
selectedDateList | 被選中的日期,用於多選 | 預設為空Set, Set selectedDateList = new Set() |
selectDateModel | 當前選擇項,用於單選 | 預設為空 |
maxMultiSelectCount | 多選,最多選多少個 | hhh |
extraDataMap | 自定義額外的資料 | 預設為空Map,Map<DateTime, Object> extraDataMap = new Map() |
UI繪製相關的配置
屬性 | 含義 | 預設值 |
---|---|---|
weekBarItemWidgetBuilder | 建立頂部的weekbar | 預設樣式 |
dayWidgetBuilder | 建立日曆item | 預設樣式 |
verticalSpacing | 日曆item之間的豎直方向間距 | 預設10 |
boxDecoration | 整體的背景設定 | |
itemSize | 每個item的邊長 | 預設是螢幕寬度/7 |
事件監聽的配置
方法 | 含義 | 預設值 |
---|---|---|
void addMonthChangeListener(OnMonthChange listener) | 月份切換事件 | |
void addOnCalendarSelectListener(OnCalendarSelect listener) | 點選選擇事件 | |
void addOnMultiSelectOutOfRangeListener(OnMultiSelectOutOfRange listener) | 多選超出指定範圍 | |
void addOnMultiSelectOutOfSizeListener(OnMultiSelectOutOfSize listener) | 多選超出限制個數 | |
void addExpandChangeListener(ValueChanged expandChange) | 監聽日曆的展開收縮狀態 |
利用controller來控制日曆的切換,支援配置動畫
方法 | 含義 | 預設值 |
---|---|---|
Future previousPage() | 滑動到上一個頁面,會自動根據當前的展開狀態,滑動到上一個月或者上一個星期。如果已經在第一個頁面,沒有上一個頁面,就會返回false,其他情況返回true | |
Future nextPage() | 滑動到下一個頁面,會自動根據當前的展開狀態,滑動到下一個月或者下一個星期。如果已經在最後一個頁面,沒有下一個頁面,就會返回false,其他情況返回true | |
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() | 切換到上一個月份 | |
void toggleExpandStatus() | 切換展開狀態 |
利用controller來獲取日曆的一些資料資訊
方法 | 含義 | 預設值 |
---|---|---|
DateTime getCurrentMonth() | 獲取當前的月份 | |
Set getMultiSelectCalendar() | 獲取被選中的日期,多選 | |
DateModel getSingleSelectCalendar() | 獲取被選中的日期,單選 |
如何自定義UI
包括自定義WeekBar、自定義日曆Item,預設使用的都是DefaultXXXWidget。
只要繼承對應的Base類,實現相應的方法,然後只需要在配置Controller的時候,實現相應的Builder方法就可以了。
//支援自定義繪製
DayWidgetBuilder dayWidgetBuilder; //建立日曆item
WeekBarItemWidgetBuilder 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);
}
}
複製程式碼
根據實際場景,自定義額外的資料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;
複製程式碼
自定義各種標記
//外部處理每個dateModel所對應的標記
Map<DateModel, String> customExtraData = {
DateModel.fromDateTime(DateTime.now().add(Duration(days: -1))): "假",
DateModel.fromDateTime(DateTime.now().add(Duration(days: -2))): "遊",
DateModel.fromDateTime(DateTime.now().add(Duration(days: -3))): "事",
DateModel.fromDateTime(DateTime.now().add(Duration(days: -4))): "班",
DateModel.fromDateTime(DateTime.now().add(Duration(days: -5))): "假",
DateModel.fromDateTime(DateTime.now().add(Duration(days: -6))): "遊",
DateModel.fromDateTime(DateTime.now().add(Duration(days: 2))): "遊",
DateModel.fromDateTime(DateTime.now().add(Duration(days: 3))): "事",
DateModel.fromDateTime(DateTime.now().add(Duration(days: 4))): "班",
DateModel.fromDateTime(DateTime.now().add(Duration(days: 5))): "假",
DateModel.fromDateTime(DateTime.now().add(Duration(days: 6))): "遊",
DateModel.fromDateTime(DateTime.now().add(Duration(days: 7))): "事",
DateModel.fromDateTime(DateTime.now().add(Duration(days: 8))): "班",
};
//建立CalendarController物件的時候,將extraDataMap賦值就行了
new CalendarController(
extraDataMap: customExtraData)
//繪製DayWidget的時候,可以直接從dateModel的extraData物件中拿到想要的資料
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 | false |
isLeapYear | 是否是閏年 | bool | false |
isWeekend | 是否是週末 | bool | false |
isInRange | 是否在範圍內,比如可以實現在某個範圍外,設定置灰的功能 | bool | false |
isSelected | 是否被選中,用來實現一些標記或者選擇功能 | bool | false |
extraData | 自定義的額外資料 | Object | 預設為空 |
方法 | 含義 |
---|---|
DateTime getDateTime() | 將DateModel轉化成DateTime |
DateModel fromDateTime(DateTime dateTime) | 根據DateTime建立對應的model,並初始化農曆和傳統節日等資訊 |
bool operator ==(Object other) | 重寫==方法,可以判斷兩個dateModel是否是同一天 |