Flutter自定义日历的实现

28次阅读

共计 17601 个字符,预计需要花费 45 分钟才能阅读完成。

红衣佳人白衣友,朝与同歌暮同酒。
世人皆谓慕长安,然吾只恋长安某。

前言

最近我们的 UI 小姐姐给了一份这样的日历设计图 ┭┮﹏┭┮,
可以上下滑动,支持多日选择,再次进入日历页面可以选中上次选中的日期,
开始想冒着侥幸的心里去找找网上的开源库,
无奈找了许久找不到可以上下滑动的日历,
故花了三天时间终于写完了初版

UI 设计的样子

默认状态下是这个样子:

选中状态下是这个样子:

TimeUtil 的实现

TimeUtil 提供时间的计算功能类

     /*
      * 每个月对应的天数
      * */
      static const List<int> _daysInMonth = <int>[
        31,
        -1,
        31,
        30,
        31,
        30,
        31,
        31,
        30,
        31,
        30,
        31
      ];
    /*
      * 根据年月获取月的天数
      * */
      static int getDaysInMonth(int year, int month) {if (month == DateTime.february) {
          final bool isLeapYear =
              (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
          if (isLeapYear) return 29;
          return 28;
        }
        return _daysInMonth[month - 1];
      }
    /*
      * 得到这个月的第一天是星期几(0 是 星期日 1 是 星期一...)* */
      static int computeFirstDayOffset(int year, int month, MaterialLocalizations localizations) {
        // 0-based day of week, with 0 representing Monday.
        final int weekdayFromMonday = DateTime(year, month).weekday - 1;
        // 0-based day of week, with 0 representing Sunday.
        final int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex;
        // firstDayOfWeekFromSunday recomputed to be Monday-based
        final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7;
        // Number of days between the first day of week appearing on the calendar,
        // and the day corresponding to the 1-st of the month.
        return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
      }
    /*
      * 每个月前面空出来的天数
      * */
      static int numberOfHeadPlaceholderForMonth(int year, int month, MaterialLocalizations localizations) {return computeFirstDayOffset(year, month, localizations);
      }
    /*
      * 根据当前年月份计算当前月份显示几行
      * */
      static int getRowsForMonthYear(int year, int month, MaterialLocalizations localizations){int currentMonthDays = getDaysInMonth(year, month);
        // 每个月前面空出来的天数
        int placeholderDays = numberOfHeadPlaceholderForMonth(year, month, localizations);
        int rows = (currentMonthDays + placeholderDays)~/7; // 向下取整
        int remainder = (currentMonthDays + placeholderDays)%7; // 取余(最后一行的天数)if (remainder > 0) {rows += 1;}
        return rows;
      }
    /*
      * 根据当前年月份计算每个月后面空出来的天数
      * */
      static int getLastRowDaysForMonthYear(int year, int month, MaterialLocalizations localizations){
        int count = 0;
        // 当前月份的天数
        int currentMonthDays = getDaysInMonth(year, month);
        // 每个月前面空出来的天数
        int placeholderDays = numberOfHeadPlaceholderForMonth(year, month, localizations);
        int rows = (currentMonthDays + placeholderDays)~/7; // 向下取整
        int remainder = (currentMonthDays + placeholderDays)%7; // 取余(最后一行的天数)if (remainder > 0) {count = 7-remainder;}
        return count;
      }

CalendarViewModel 的实现

CalendarViewModel 提供日历要显示的数据模型

    class YearMonthModel {
      int year;
      int month;
    
      YearMonthModel(this.year, this.month);
    }
    // 每天对应的数据模型
    class DayModel {
      int year;
      int month;
      int dayNum; // 数字类型的几号
      String day; // 字符类型的几号
      bool isSelect; // 是否选中
      bool isOverdue; // 是否过期
      DayModel(this.year, this.month, this.dayNum, this.day, this.isSelect, this.isOverdue);
    }
    // 每个月对应的数据模型
    class CalendarItemViewModel {
      final List<DayModel> list;
      final int year;
      final int month;
      DayModel firstSelectModel;
      DayModel lastSelectModel;
      CalendarItemViewModel({this.list, this.year, this.month, this.firstSelectModel, this.lastSelectModel});
    }
    
    class CalendarViewModel {List<YearMonthModel> yearMonthList = CalendarViewModel.getYearMonthList();
    
      List<CalendarItemViewModel> getItemList() {List<CalendarItemViewModel> _list = [];
        yearMonthList.forEach((model){List<DayModel> dayModelList = getDayModelList(model.year, model.month);
          _list.add(CalendarItemViewModel(list:dayModelList,year: model.year, month:model.month));
        });
        return _list;
      }
      // 根据年月得到 月的每天显示需要的日期
      static List<DayModel> getDayModelList(int year, int month) {List<DayModel> _listModel = [];
        // 今天几号
        int  _currentDay = DateTime.now().day;
        // 今天在几月
        int _currentMonth = DateTime.now().month;
        // 当前月的天数
        int _days = TimeUtil.getDaysInMonth(year, month);
    
        String _day = '';
        bool _isSelect = false;
        bool isOverdue = false;
        int _dayNum = 0;
        for (int i = 1; i <= _days; i++) {
          _dayNum = i;
          if (_currentMonth == month) {
            // 在当前月
            if (i < _currentDay) {
              isOverdue = true;
              _day = '$i';
            } else if (i == _currentDay) {
              _day = '今';
              isOverdue = false;
            } else {
              _day = '$i';
              isOverdue = false;
            }
          } else {
            _day = '$i';
            isOverdue = false;
          }
          DayModel dayModel = DayModel(year, month, _dayNum, _day, _isSelect, isOverdue);
          _listModel.add(dayModel);
        }
        return _listModel;
      }
    
      /*
      * 根据当前年月份计算下面 6 个月的年月,根据需要可以实现更多个月的
      * */
      static List<YearMonthModel> getYearMonthList() {int _month = DateTime.now().month;
        int _year = DateTime.now().year;
    
        List<YearMonthModel> _yearMonthList = <YearMonthModel>[];
        for(int i=0; i<6; i++) {YearMonthModel model = YearMonthModel(_year, _month);
          _yearMonthList.add(model);
          if(_month == 12) {
            _month = 1;
            _year ++;
    
          } else {_month ++;}
        }
        return _yearMonthList;
      }

CalendarItem 的实现

CalendarItem 对应的是每个月的 widget

    typedef void OnTapDayItem(int year, int month, int checkInTime);
    
    class CalendarItem extends StatefulWidget {
      final CalendarItemViewModel itemModel;
      final OnTapDayItem dayItemOnTap;
    
      CalendarItem(this.dayItemOnTap, this.itemModel);
    
      @override
      _CalendarItemState createState() => _CalendarItemState();
    }
    
    class _CalendarItemState extends State<CalendarItem> {
      // 日历显示几行
      int _rows = 0;
      List<DayModel> _listModel = <DayModel>[];
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        _listModel = widget.itemModel.list;
      }
    
      @override
      Widget build(BuildContext context) {double screenWith = MediaQuery.of(context).size.width;
        // 显示几行
        _rows = TimeUtil.getRowsForMonthYear(widget.itemModel.year,
            widget.itemModel.month, MaterialLocalizations.of(context));
    
        return Container(
          width: screenWith,
          height: 25.0 + 24.0 + 17.0 + _rows * 52.0 + 32.0 + 13,
          child: Column(
            children: <Widget>[
              SizedBox(height: 32,),
              _yearMonthItem(widget.itemModel.year, widget.itemModel.month),
              SizedBox(height: 24,),
              _weekItem(screenWith),
              SizedBox(height: 13,),
              _monthAllDays(widget.itemModel.year, widget.itemModel.month, context),
            ],
          ),
        );
      }
    
      /*
      * 显示年月的组件,需要传入年月日期
      * */
      _yearMonthItem(int year, int month) {
        return Container(
          alignment: Alignment.center,
          height: 25,
          child: Text(
            '$year.$month',
            style: TextStyle(color: ColorUtil.color('212121'),
              fontSize: 18,
              fontFamily: 'Avenir-Heavy',
            ),
          ),
        );
      }
    
      /*
      * 显示周的组件,使用了 _weekTitleItem
      * */
      _weekItem(double screenW) {
        List<String> _listS = <String>[
          '日',
          '一',
          '二',
          '三',
          '四',
          '五',
          '六',
        ];
        List<Widget> _listW = [];
        _listS.forEach((title) {_listW.add(_weekTitleItem(title, (screenW - 40) / 7));
        });
        return Container(
          width: screenW - 40,
          height: 17,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: _listW,
          ),
        );
      }
    
      /*
      * 周内对应的每天的组件
      * */
      _weekTitleItem(String title, double width) {
        return Container(
          alignment: Alignment.center,
          width: width,
          child: Text(
            title,
            style: TextStyle(color: ColorUtil.color('757575'),
              fontSize: 12,
              fontFamily: 'PingFangSC-Semibold',
            ),
          ),
        );
      }
    
      _monthAllDays(int year, int month, BuildContext context) {double screenWith = MediaQuery.of(context).size.width;
    
        // 当前月前面空的天数
        int emptyDays = TimeUtil.numberOfHeadPlaceholderForMonth(year, month, MaterialLocalizations.of(context));
    
        List<Widget> _list = <Widget>[];
    
        for (int i = 1; i <= emptyDays; i++) {_list.add(_dayEmptyTitleItem(context));
        }
    
        for (int i = 1; i <= _listModel.length; i++) {_list.add(_dayTitleItem(_listModel[i - 1], context));
        }
    
        List<Row> _rowList = <Row>[
          Row(children: _list.sublist(0, 7),
          ),
          Row(children: _list.sublist(7, 14),
          ),
          Row(children: _list.sublist(14, 21),
          ),
        ];
    
        if (_rows == 4) {
          _rowList.add(
            Row(children: _list.sublist(21, _list.length),
            ),
          );
        } else if (_rows == 5) {
          _rowList.add(
            Row(children: _list.sublist(21, 28),
            ),
          );
          _rowList.add(
            Row(children: _list.sublist(28, _list.length),
            ),
          );
        } else if (_rows == 6) {
          _rowList.add(
            Row(children: _list.sublist(21, 28),
            ),
          );
          _rowList.add(
            Row(children: _list.sublist(28, 25),
            ),
          );
          _rowList.add(
            Row(children: _list.sublist(35, _list.length),
            ),
          );
        }
        return Container(
          width: screenWith - 40,
          color: Colors.white,
          height: 52.0 * _rows,
          child: Column(children: _rowList,),
        );
      }
    
      /*
      * number 月的几号
      * isOverdue 是否过期
      * */
      _dayTitleItem(DayModel model, BuildContext context) {double screenWith = MediaQuery.of(context).size.width;
        double singleW = (screenWith - 40) / 7;
        String dayTitle = model.day;
        if (widget.itemModel.firstSelectModel != null &&
            model.isSelect &&
            model.dayNum == widget.itemModel.firstSelectModel.dayNum) {dayTitle = '入住';}
        if (widget.itemModel.lastSelectModel != null &&
            model.isSelect &&
            model.dayNum == widget.itemModel.lastSelectModel.dayNum) {dayTitle = '离开';}
        return GestureDetector(onTap: () {if(model.isOverdue) return;
            _dayTitleItemTap(model);
          },
          child: Stack(
            children: <Widget>[
              Container(
                width: singleW,
                height: 52,
                alignment: Alignment.center,
                child: Text(
                  dayTitle,
                  style: TextStyle(
                    color: model.isOverdue
                        ? ColorUtil.color('BDBDBD')
                        : ColorUtil.color('212121'),
                    fontSize: 15,
                    fontFamily: 'Avenir-Medium',
                  ),
                ),
              ),
              Positioned(
                left: 0,
                right: 0,
                bottom: 0,
                child: Visibility(
                    visible: model.isOverdue ? false : model.isSelect,
                    child: Container(
                      height: 4,
                      width: singleW,
                      color: ColorUtil.color('FED836'),
                    )),
              ),
            ],
          ),
        );
      }
    
      _dayEmptyTitleItem(BuildContext context) {double screenWith = MediaQuery.of(context).size.width;
        double singleW = (screenWith - 40) / 7;
        return Container(
          width: singleW,
          height: 52,
        );
      }
    
      _dayTitleItemTap(DayModel model) {
        widget.dayItemOnTap(widget.itemModel.year, widget.itemModel.month, model.dayNum);
        setState(() {});
      }
    }    

CalendarPage 的实现

CalendarPage 是日历页面的 Widget

    /*
    * Location 标记当前选中日期和之前的日期相比,* left:是在之前日期之前
    * mid:和之前日期相等
    * right:在之前日期之后
    * */
    enum Location{left,mid,right}
    
    typedef void SelectDateOnTap(DayModel checkInTimeModel, DayModel leaveTimeModel);
    
    class CalendarPage extends StatefulWidget {
      final DayModel startTimeModel;// 外部传入的之前选中的入住日期
      final DayModel endTimeModel;// 外部传入的之前选中的离开日期
      final SelectDateOnTap selectDateOnTap;// 确定按钮的 callback 给外部传值
      CalendarPage({this.startTimeModel,this.endTimeModel,this.selectDateOnTap});
    
      @override
      _CalendarPageState createState() => _CalendarPageState();
    }
    
    class _CalendarPageState extends State<CalendarPage> {
    
      String _selectCheckInTime = '选择入住时间';
      String _selectLeaveTime = '选择离开时间';
      bool _isSelectCheckInTime = false; // 是否选择入住日期
      bool _isSelectLeaveTime = false; // 是否选择离开日期
      int _checkInDays = 0; // 入住天数
    
      // 保存当前选中的入住日期和离开日期
      DayModel _selectCheckInTimeModel = null;
      DayModel _isSelectLeaveTimeModel = null;
    
      List<CalendarItemViewModel> _list = [];
      @override
      void initState() {super.initState();
        // 加载日历数据源
        _list = CalendarViewModel().getItemList();
        // 处理外部传入的选中日期
        if(widget.startTimeModel!=null && widget.endTimeModel!=null) {for(int i=0; i<_list.length; i++) {CalendarItemViewModel model = _list[i];
            if(model.month == widget.startTimeModel.month) {_updateDataSource(widget.startTimeModel.year, widget.startTimeModel.month, widget.startTimeModel.dayNum);
            }
            if (model.month == widget.endTimeModel.month) {_updateDataSource(widget.endTimeModel.year, widget.endTimeModel.month, widget.endTimeModel.dayNum);
            }
          }
        }
      }
      @override
      Widget build(BuildContext context) {final data = MediaQuery.of(context);
        // 屏幕宽高
        final screenHeight = data.size.height;
        final screenWidth = data.size.width;
        return Container(
          color: Colors.white,
          width: double.maxFinite,
          height: screenHeight - 64,
          child: Stack(
            children: <Widget>[
              Column(
                children: <Widget>[
                  SizedBox(height: 86,),
                  Row(
                    children: <Widget>[
                      SizedBox(width: 20,),
                      // 择入住时间的视图
                      _selectTimeItem(context, _selectCheckInTime,
                          Alignment.centerLeft, _isSelectCheckInTime),
                      // 入住天数的视图
                      _daysItem(_checkInDays),
                      // 选择离开时间的视图
                      _selectTimeItem(context, _selectLeaveTime,
                          Alignment.centerRight, _isSelectLeaveTime),
                      SizedBox(width: 20,),
                    ],
                  ),
    
                  // 月日期的视图
                  Container(
                    height: screenHeight - 64 - 80 - 83 - 30,
                    child: ListView.builder(itemBuilder: (BuildContext context, int index) {CalendarItemViewModel itemModel = _list[index];
                        return CalendarItem((year, month, checkInTime) {
                            _updateCheckInLeaveTime(year, month, checkInTime);
                          },
                          itemModel,
                        );
                      },
                      itemCount: _list.length,
                    ),
                  ),
                ],
              ),
              Positioned(
                left: 0,
                right: 0,
                bottom: 0,
                height: MediaQuery.of(context).padding.bottom,
                child: Container(),),
              _bottonSureButton(screenWidth),
            ],
          ),
        );
      }
    
      /*
      * content 显示的日期
      * alignment 用来控制文本的对齐方式
      * isSelectTime 是否选择了日期
      * */
      _selectTimeItem(BuildContext context, String content, Alignment alignment,
          bool isSelectTime) {final screenWidth = MediaQuery.of(context).size.width;
        return Container(width: (screenWidth - 40 - 30) / 2,
          height: 30,
          alignment: alignment,
          child: Text(
            content,
            style: TextStyle(
              fontFamily: isSelectTime ? 'Avenir-Heavy' : 'PingFangSC-Regular',
              fontSize: isSelectTime ? 22 : 18,
              color: isSelectTime
                  ? ColorUtil.color('212121')
                  : ColorUtil.color('BDBDBD'),
            ),
          ),
        );
      }
    
      /*
      * day 入住天数,默认不选择为 0
      * */
      _daysItem(int day) {
        return Container(
          width: 30,
          height: 18,
          alignment: Alignment.center,
          decoration: BoxDecoration(
            color: Colors.white,
            border: Border.all(width: 0.5, color: ColorUtil.color('BDBDBD')),
            borderRadius: BorderRadius.all(Radius.circular(2)),
          ),
          child: Text(
            '$day 晚',
            style: TextStyle(color: ColorUtil.color('BDBDBD'),
              fontSize: 12,
            ),
          ),
        );
      }
    
      /*
      * 底部确定按钮
      * */
      _bottonSureButton(double screenWidth) {
        return Positioned(
          left: 0,
          right: 0,
          bottom: MediaQuery.of(context).padding.bottom,
          height: 80,
          child: Container(
            height: 80,
            width: double.maxFinite,
            color: Colors.white,
            alignment: Alignment.center,
            child: GestureDetector(
              onTap: _sureButtonTap,
              child: Container(
                height: 48,
                width: screenWidth - 30,
                decoration: BoxDecoration(color: ColorUtil.color('FED836'),
                  borderRadius: BorderRadius.all(Radius.circular(24.0)),
                ),
                child: Center(
                  child: Text(
                    '确定',
                    style: TextStyle(
                      fontSize: 16,
                      color: Colors.black,
                      fontFamily: 'PingFangSC-Light',
                    ),
                  ),
                ),
              ),
            ),
          ),
        );
      }
    
      /*
      * 比较后面的日期是比 model 日期小(left)还是相等(mid) 还是大 (right)
      * */
      _comparerTime(DayModel model, int year, int month, int day){if(year > model.year) {return Location.right;} else if(year == model.year) {if(model.month < month) {return Location.right;} else if(model.month == month){if(model.dayNum < day){return Location.right;} else if(model.dayNum == day){return Location.mid;} else {return Location.left;}
          } else {return Location.right;}
        } else {return Location.left;}
      }
    
      /*
      * 更新日历的数据源
      * */
      _updateDataSource(int year, int month, int checkInTime) {
        // 左右指针 用来记录选择的入住日期和离开日期
        DayModel firstModel = null ;
        DayModel lastModel = null;
    
        for(int i=0; i<_list.length; i++) {CalendarItemViewModel model = _list[i];
          if(model.firstSelectModel != null){firstModel = model.firstSelectModel;}
          if (model.lastSelectModel != null) {lastModel = model.lastSelectModel;}
        }
    
        if (firstModel != null && lastModel != null) {for(int i=0; i<_list.length; i++) {CalendarItemViewModel model = _list[i];
            model.firstSelectModel = null;
            model.lastSelectModel = null;
            firstModel = null;
            lastModel = null;
            for(int i=0; i<model.list.length; i++) {DayModel dayModel = model.list[i];
              dayModel.isSelect = false;
              if(_comparerTime(dayModel, year, month, checkInTime) == Location.mid){
                dayModel.isSelect = true;
                model.firstSelectModel = dayModel;
                _isSelectCheckInTime = true;
                _isSelectLeaveTime = false;
                _selectCheckInTime = '$year.$month.$checkInTime';
                _selectCheckInTimeModel = dayModel;
              }
            }
          }
          _checkInDays = 0;
          _isSelectLeaveTime = false;
          _selectLeaveTime = '选择离开时间';
          _isSelectLeaveTimeModel = null;
        } else if(firstModel != null && lastModel == null) {if(_comparerTime(firstModel, year, month, checkInTime) == Location.left){for(int i=0; i<_list.length; i++) {CalendarItemViewModel model = _list[i];
              model.firstSelectModel = null;
              model.lastSelectModel = null;
              firstModel = null;
              lastModel = null;
              for(int i=0; i<model.list.length; i++) {DayModel dayModel = model.list[i];
                dayModel.isSelect = false;
                if(_comparerTime(dayModel, year, month, checkInTime) == Location.mid){
                  dayModel.isSelect = !dayModel.isSelect;
                  model.firstSelectModel = dayModel;
                  _isSelectCheckInTime = dayModel.isSelect ? true : false;
                  _selectCheckInTime = '$year.$month.$checkInTime';
                  _selectCheckInTimeModel = dayModel;
                }
              }
            }
            _checkInDays = 0;
            _isSelectLeaveTime = false;
            _selectLeaveTime = '选择离开时间';
            _isSelectLeaveTimeModel = null;
          } else if(_comparerTime(firstModel, year, month, checkInTime) == Location.mid){// 点击了自己
            for(int i=0; i<_list.length; i++) {CalendarItemViewModel model = _list[i];
              model.lastSelectModel = null;
              if(model.month == month){for(int i=0; i<model.list.length; i++) {DayModel dayModel = model.list[i];
                  if(_comparerTime(dayModel, year, month, checkInTime) == Location.mid){
                    dayModel.isSelect = !dayModel.isSelect;
                    model.firstSelectModel = dayModel.isSelect ? dayModel : null;
                    _selectCheckInTimeModel = dayModel.isSelect ? dayModel : null;
                    _isSelectCheckInTime = dayModel.isSelect ? true : false;
                    _selectCheckInTime = dayModel.isSelect ? '$year.$month.$checkInTime' : '选择入住时间';
                  }
                }
              }
            }
            _checkInDays = 0;
            _isSelectLeaveTime = false;
            _selectLeaveTime = '选择离开时间';
            _isSelectLeaveTimeModel = null;
          } else if (_comparerTime(firstModel, year, month, checkInTime) == Location.right){if(month == firstModel.month){
              // 统计入住天数
              int _calculaterDays = 1;
              for(int i=0; i<_list.length; i++) {CalendarItemViewModel model = _list[i];
                if(model.month == month){for(int i=0; i<model.list.length; i++) {DayModel dayModel = model.list[i];
                    if(dayModel.dayNum == checkInTime) {
                      dayModel.isSelect = true;
                      model.lastSelectModel = dayModel;
                      _isSelectLeaveTimeModel = dayModel;
                      _isSelectLeaveTime = true;
                      _selectLeaveTime = '$year.$month.$checkInTime';
                    }else if(dayModel.dayNum > firstModel.dayNum && dayModel.dayNum<checkInTime){
                      dayModel.isSelect = true;
                      _calculaterDays++;
                    }
                  }
                }
              }
              _checkInDays = _calculaterDays;
            } else {
              // 统计入住天数
              int _calculaterDays = 1;
              for(int i=0; i<_list.length; i++) {CalendarItemViewModel model = _list[i];
                if(model.month == firstModel.month){for(int i=0; i<model.list.length; i++) {DayModel dayModel = model.list[i];
                    if (dayModel.dayNum > firstModel.dayNum){
                      dayModel.isSelect = true;
                      _calculaterDays++;
                    }
                  }
                } else if(model.month>firstModel.month && model.month<month){for(int i=0; i<model.list.length; i++) {DayModel dayModel = model.list[i];
                    dayModel.isSelect = true;
                    _calculaterDays++;
                  }
                } else if(month == model.month){for(int i=0; i<model.list.length; i++) {DayModel dayModel = model.list[i];
                    if(dayModel.dayNum < checkInTime){
                      dayModel.isSelect = true;
                      _calculaterDays++;
                    } else if (dayModel.dayNum == checkInTime) {
                      dayModel.isSelect = true;
                      model.lastSelectModel = dayModel;
                      _isSelectLeaveTimeModel = dayModel;
                      _isSelectLeaveTime = true;
                      _selectLeaveTime = '$year.$month.$checkInTime';
                    }
                  }
                }
              }
              _checkInDays = _calculaterDays;
            }
          }
        } else if(firstModel == null && lastModel == null){for(int i=0; i<_list.length; i++) {CalendarItemViewModel model = _list[i];
            model.firstSelectModel = null;
            model.lastSelectModel = null;
            firstModel = null;
            lastModel = null;
            for(int i=0; i<model.list.length; i++) {DayModel dayModel = model.list[i];
              dayModel.isSelect = false;
              if(_comparerTime(dayModel, year, month, checkInTime) == Location.mid){
                dayModel.isSelect = true;
                model.firstSelectModel = dayModel;
                _isSelectCheckInTime = true;
                _selectCheckInTimeModel = dayModel;
                _isSelectLeaveTime = false;
                _selectCheckInTime = '$year.$month.$checkInTime';
              }
            }
          }
        }
      }
    
      /*
      * 点击日期的回调事件
      * */
      _updateCheckInLeaveTime(int year, int month, int checkInTime) {
        // 更新数据源
        _updateDataSource(year, month, checkInTime);
        // 刷新 UI
        setState(() {});
      }
    
      /*
      * 底部确定按钮的点击事件
      * */
      _sureButtonTap() {if(!_isSelectCheckInTime){ShowToast().showToast('请选择入住时间');
          return;
        } else if (!_isSelectLeaveTime){ShowToast().showToast('请选择离开时间');
          return;
        }
        print('${_selectCheckInTimeModel.year},${_selectCheckInTimeModel.month},${_selectCheckInTimeModel.dayNum}');
        print('${_isSelectLeaveTimeModel.year},${_isSelectLeaveTimeModel.month},${_isSelectLeaveTimeModel.dayNum}');
        print('入住日期:$_selectCheckInTime, 离开时间:$_selectLeaveTime, 共 $_checkInDays 晚');
        // 把日期回调给外部
        widget.selectDateOnTap(_selectCheckInTimeModel,_isSelectLeaveTimeModel);
        Navigator.pop(context);
      }
    }

状态设计模式

日历除了 UI 视图外,最麻烦的就是选择日期的各种逻辑
这里我们把它 抽象 成几种 状态 之间的转换:

对应的代码逻辑就是:CalendarPage 页面中更新日历的数据源的方法(_updateDataSource)

正文完
 0