flutter 自定义日历选择

禁止转载、抄袭

功能需求

实现后是长这样的(因为项目要以底部弹窗显示,也可以整个UI自定义)
要代码的私信我(看人品回复)
请添加图片描述
项目需求:

  1. 星期一在第一,星期日在最后
  2. 一开始显示的是当前月份
  3. 有startTime和endTime的限制,所以并不是每个日期都能选择
  4. 选择全部工作日按钮,意思是除了星期六日和第三点的条件都选择
  5. 已选统计选了多少天,下一步把选择的传到下个页面
  6. 没有说需不需要滑动切换月份(我是没做了)
  7. 在第3点外的月份不能点击全选
  8. 新增一个带回显的功能

干就完事了

我看了日历第三方库,有些符合要求,但有些不符合要求(淦),看了原理直接手撕算了。

实际原理就是gradeview(iOS里的collectionView),里面添数据而已,就是处理数据比较麻烦,幸好没叫我做滑动。

说这么多,上核心代码

因为部分是项目的代码,我不可能公开,你需要的话可以私信我,女的请私信你的qq(感觉不可能)

工具类

这些函数的dateTime date都是该月的第一天,其他不管用(主要是自己懒得再重新处理数据)


  ///星期一为第一天
  static int computeFirstDay(
      DateTime date, MaterialLocalizations localizations) {
    final int weekdayFromMonday = date.weekday;
    final int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex + 1;
    final int firstDayOfWeekFromMonday = firstDayOfWeekFromSunday % 7;
    return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
  }
  static const List<int> _daysInMonth = <int>[
    31,
    -1,
    31,
    30,
    31,
    30,
    31,
    31,
    30,
    31,
    30,
    31
  ];
  /// 获取这个月的天数
  static int getDayCountInMonth(DateTime date) {
    int month = date.month;
    int year = date.year;
    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];
  }

这些大概是定位该月gradeView(collectionView)的显示数据位置。

UI方面的代码我不怎么想给,毕竟产品+UI都不一样

调用
List<DateModel> list = [];
  List<DateTime> returnList = [];
  List<TotalDateModel> totalList = [];
  bool isfullChoice = false;
  bool canChooseMoon = true;
@override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
      setState(() {
        list = getDateModel(MaterialLocalizations.of(context));
      });

    });
  }


class DateModel {
  int day;
  bool isChoosen;
  DateModel({
    required this.day,
    required this.isChoosen,
  });
}
创建原始数据
///创建数据
  List<DateModel> getDateModel(MaterialLocalizations localizations){
    //当月的总天数
    final int currentMonthTotalDays = CommonUtils.getMonthDay(widget.date);
    //每月的一号对应的周几(星期一在第二天)若星期一第一天就-1
    final int firstDayIsWeekInMonth = CommonUtils.computeFirstDay(widget.date, localizations);
    final List<DateModel> dayList = [];
    for(int i = 0;i < firstDayIsWeekInMonth; i++) {
      final DateModel model = DateModel(day: 0,isChoosen: false);
      dayList.add(model);
    }
    for(int i = 1; i <= currentMonthTotalDays; i ++) {
      final DateModel model = DateModel(day: i,isChoosen: false);
      dayList.add(model);
    }
    return dayList;
  }
切换月份
//切换月份处理事件 isRight为1 就+月,-1就-月
  void changeMonth(int isRight) {
    //寻找list里的元素内容,若找不到返回orelse TotalDateModel
    totalList.firstWhere((element) => element.date == widget.date, orElse: () {
      final TotalDateModel newModel = TotalDateModel(date: widget.date, dateList: list);
      totalList.add(newModel);
      return newModel;
    }).dateList = list;

    final DateTime nextDate = DateTime(widget.date.year, widget.date.month + isRight, widget.date.day);
    widget.date = nextDate;
    if(widget.goNextMonth != null) {
      widget.goNextMonth(widget.date);
    }
    list = totalList.firstWhere((element) => element.date == nextDate, orElse: () {
      list = getDateModel(MaterialLocalizations.of(context));
      final TotalDateModel newModel = TotalDateModel(dateList: list);
      return newModel;
    }).dateList;

    //是否全选
    final int firstDayIsWeekInMonth =
        CommonUtils.computeFirstDay(widget.date, MaterialLocalizations.of(context));


    isfullChoice = true;
    canChooseMoon = true;
    ///遍历date的数组,起始时间和终止时间内false
    for (int j = firstDayIsWeekInMonth; j < list.length; j++) {
      final int index = list[j].day + firstDayIsWeekInMonth;
      if(index%7 != 0 && (index + 1)%7 != 0){
        if (list[j].isChoosen == false) {
          final DateTime dateTime = DateTime(widget.date.year,widget.date.month,list[j].day);
          final epochTime = dateTime.millisecondsSinceEpoch;
          if ((widget.model.validStartDate ?? 0) <= epochTime && epochTime <= (widget.model.validEndDate ?? 0)){
            isfullChoice = false;
            break;
          }
        }
      }
    }
    ///该月份是否能点击全选
    final int startTime = DateTime(widget.date.year,widget.date.month,1).millisecondsSinceEpoch;
    final int endTime = DateTime(widget.date.year,widget.date.month,list[list.length - 1].day).millisecondsSinceEpoch;
    if((widget.model.validStartDate ?? 0) > endTime || (widget.model.validEndDate ?? 0) < startTime) {
      canChooseMoon = false;
      isfullChoice = false;
    }
    setState(() {});
  }
全选按钮
void chooseAllAction() {
    //每月的一号对应的周几(星期一在第二天)若星期一第一天就-1
    final int firstDayIsWeekInMonth = CommonUtils.computeFirstDay(widget.date, MaterialLocalizations.of(context));
    //反选
    if(isfullChoice) {
      for(int i = firstDayIsWeekInMonth;i < list.length;i++){
        if (list[i].isChoosen) {
          final int index = list[i].day + firstDayIsWeekInMonth;
          if(index%7 != 0 && (index + 1)%7 != 0){
            final DateTime dateTime = DateTime(widget.date.year,widget.date.month,list[i].day);
            final epochTime = dateTime.millisecondsSinceEpoch;
            if ((widget.model.validStartDate ?? 0) <= epochTime && epochTime <= (widget.model.validEndDate ?? 0)){
              list[i].isChoosen = false;
              returnList.remove(dateTime);
            }
          }
        }
      }
    }else {      //正选
      for(int i = firstDayIsWeekInMonth;i < list.length;i++){
        if (!list[i].isChoosen) {
          final int index = list[i].day + firstDayIsWeekInMonth;
          if(index%7 != 0 && (index + 1)%7 != 0){
            final DateTime dateTime = DateTime(widget.date.year,widget.date.month,list[i].day);
            final epochTime = dateTime.millisecondsSinceEpoch;
            if ((widget.model.validStartDate ?? 0) <= epochTime &&
                epochTime <= (widget.model.validEndDate ?? 0)){
              if (list[i].isChoosen == false) {
                returnList.add(dateTime);
              }
              list[i].isChoosen = true;
            }
          }
        }
      }
    }
    setState(() {
      isfullChoice = !isfullChoice;
    });
  }

点击单个item的处理事件

onTap: (){
              //每月的一号对应的周几(星期一在第二天)若星期一第一天就-1
              final int firstDayIsWeekInMonth = CommonUtils.computeFirstDay(widget.date, MaterialLocalizations.of(context));
              final DateTime dateTime = DateTime(widget.date.year,widget.date.month,list[i].day);
              final epochTime = dateTime.millisecondsSinceEpoch;
              if ((widget.model.validStartDate ?? 0) <= epochTime && epochTime <= (widget.model.validEndDate ?? 0)){
                list[i].isChoosen = !list[i].isChoosen;
                if(list[i].isChoosen){
                  isfullChoice = true;
                  returnList.add(dateTime);
                  for(int j = firstDayIsWeekInMonth;j < list.length;j++){
                    if(list[j].isChoosen == false) {
                      final int index = list[j].day + firstDayIsWeekInMonth;
                      if(index%7 != 0 && (index + 1)%7 != 0){
                        isfullChoice = false;
                        break;
                      }
                    }
                  }
                }else {
                  final int index = list[i].day + firstDayIsWeekInMonth;
                  returnList.remove(dateTime);
                  if(index%7 != 0 && (index + 1)%7 != 0){
                    isfullChoice = false;
                  }
                }
              }
次要的判断高度
double getBottomSheetHeight(DateTime date) {
    //当月的总天数
    final int currentMonthTotalDays = CommonUtils.getMonthDay(date);
    //每月的一号对应的周几(星期一在第二天)若星期一第一天就-1
    final int firstDayIsWeekInMonth = CommonUtils.computeFirstDay(date, MaterialLocalizations.of(context));
    final int line = ((currentMonthTotalDays + firstDayIsWeekInMonth)%7).toInt() == 0 ? 0 : 1;
    final lines = ((currentMonthTotalDays + firstDayIsWeekInMonth)/7).toInt();
    int column = lines + line ;
    final itemHeight = MediaQuery.of(context).size.width/7 ;
    return column * itemHeight + 15;
  }

回显功能

带回显的功能,就需要闭包传值返回,也就是上下文传递

///回显
  void Function(List<TotalDateModel> totalList,
      List<DateModel> list,
      List<DateTime> returnList,
      bool isFullChoice,
      bool canChooseMoon,) callBackData;

在点击退出和点下一步的时候调用这个闭包,把该页面所需要的值回传。

定义的话就需要把闭包回传的值返回到这个页面

// DateTime date;
  MealOrderDetailModel model;
  String schoolName;
  int schoolId;
  int productId;
  DateTime date;
  ///存储上下月的数据
  List<TotalDateModel> totalList;
  ///页面需要的基础list
  List<DateModel> list;
  ///点击后加进去的值
  List<DateTime> returnList;

  ///全选按钮
  bool isFullChoice;
  ///startTime和endTime之外
  bool canChooseMoon;

 MealOrderCalendar({
    Key? key,
    required this.date,
    required this.model,
    required this.schoolName,
    required this.productId,
    required this.schoolId,
    required this.list,
    required this.totalList,
    required this.goNextMonth,
    required this.callBackData,
    required this.returnList,
    required this.canChooseMoon,
    required this.isFullChoice,
  }): super(key: key);

私有变量初始化赋值,我一开始用 widge.xxxx作为数据源时,出现错误(页面不更新),所以是需要私有变量来处理数据源

@override
  void initState() {
    super.initState();
    _totalList = widget.totalList;
    _list = widget.list;
    _date = widget.date;
    _isFullChoice = widget.isFullChoice;
    _canChooseMoon = widget.canChooseMoon;
    _returnList = widget.returnList;
    WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
      if(_list.isEmpty) {
      setState(() {
          _list = getDateModel(MaterialLocalizations.of(context));
      });
      }

    });
  }

分享了这么多点个赞不过分吧

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐