Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格

入魔的冬瓜 發表於 2019-10-10

FlutterCalendarWidget

Flutter上的一個日曆控制元件,可以定製成自己想要的樣子。Github地址

介紹

之前寫了一個Flutter日曆的開源庫,最近增加了一些功能,並且對程式碼進行了一下重構,再搞了下效能優化。(之前的程式碼寫得真的是****,沒搞狀態框架,還各種巢狀程式碼)

示例

日曆支援web預覽:點選此處進入預覽

Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格 Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格 Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格 Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格 Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格 Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格 Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格

主要功能

  • 支援公曆,農曆,節氣,傳統節日,常用節假日
  • 日期範圍設定,預設支援的最大日期範圍為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的進度條資料

Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格

    //外部處理每個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;
    
複製程式碼

自定義各種標記

Flutter日曆2.0,支援月檢視和周檢視,可以支援自定義風格
 //外部處理每個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是否是同一天