Flutter日曆,可以自定義風格UI

入魔的冬瓜發表於2019-05-21

介紹

最近自己寫了個demo,用到了日曆方面的東西,然後實現來一下,最後打算封裝一下,以後可以直接拿來使用。

參考了Android的一個開源日曆庫github.com/huanghaibin…,實現思路其實差不多,都是可以利用canvas將日曆給畫出來。

Flutter日曆的專案地址:github.com/LXD31256949…

示例

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

Flutter日曆,可以自定義風格UIFlutter日曆,可以自定義風格UI Flutter日曆,可以自定義風格UIFlutter日曆,可以自定義風格UI

主要功能

  • 支援公曆,農曆,節氣,傳統節日,常用節假日
  • 日期範圍設定,預設支援的最大日期範圍為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_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(); //自定義額外的資料

//各種事件回撥
OnMonthChange monthChange; //月份切換事件
OnCalendarSelect calendarSelect; //點選選擇事件
OnMultiSelectOutOfRange multiSelectOutOfRange; //多選超出指定範圍
OnMultiSelectOutOfSize multiSelectOutOfSize; //多選超出限制個數

//支援自定義繪製
DayWidgetBuilder dayWidgetBuilder; //建立日曆item
WeekBarItemWidgetBuilder 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<DateTime> selectedDateTimeList = EMPTY_SET,
      DateModel selectDateModel,
      int maxMultiSelectCount = 9999,
      Map<DateTime, Object> 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();
//獲取被選中的日期,多選
Set<DateModel> 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);
  }
}
複製程式碼

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
  • 支援周檢視
  • 支援動畫切換周檢視和月檢視

相關文章