Flutter自定义日历,实现日期底部不同颜色的日历事件

记录开发心得,提升自我成长。

本文记录没有用到任何一个第三方插件的纯自定义日历,实现日历月份切换,点击查看选中的日期事件(底部对应的日期事件未写😁,有意者可以自行添加),日期底部根据不同的状态展示不同颜色的小圆点(可用于记录考勤状态,正常还是异常),默认选中当月当天,切换月份默认选中第一天,并自动加载相应的日期事件。

话不多说,先上图(没图说个~~)

 直接上代码,代码比较简单就不分段展示了,代码中都有相应的备注

import 'package:flutter/material.dart';
import 'package:flutter_app_demo/colors.dart';
import 'package:flutter_app_demo/view/custom_appbar.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class TwoPage extends StatefulWidget {
  const TwoPage({Key? key}) : super(key: key);

  @override
  _TwoPageState createState() => _TwoPageState();
}

class _TwoPageState extends State<TwoPage> {
  int _year = DateTime.now().year;
  int _month = DateTime.now().month;
  int _day = DateTime.now().day;
  List<CalendarModel> _datas = [];
  List<CalendarModel> _list_datas = [];

  @override
  void initState() {
    // TODO: implement initState
    //设置默认当前月日期
    _setDatas(year: _year, month: _month);

    //设置模拟数据,日历月事件,可根据接口返回的结果
    _loadAttendanceMonthRecord( _year.toString() + "-" + _month.toString());
    //日历日事件
    _loadAttendanceDayRecord(_year.toString() + "-" + _month.toString()+"-"+_day.toString());

    super.initState();
  }

  //加载月历事件,请求接口
  _loadAttendanceMonthRecord(String dateTime) async {
    CalendarModel bean1= new CalendarModel(year: _year,month: _month,day: 1,work_type: "2");
    CalendarModel bean2= new CalendarModel(year: _year,month: _month,day: 2,work_type: "1");
    CalendarModel bean3= new CalendarModel(year: _year,month: _month,day: 3,work_type: "2");
    CalendarModel bean4= new CalendarModel(year: _year,month: _month,day: 4,work_type: "0");
    CalendarModel bean5= new CalendarModel(year: _year,month: _month,day: 5,work_type: "0");
    CalendarModel bean6= new CalendarModel(year: _year,month: _month,day: 6,work_type: "1");
    _list_datas.add(bean1);
    _list_datas.add(bean2);
    _list_datas.add(bean3);
    _list_datas.add(bean4);
    _list_datas.add(bean5);
    _list_datas.add(bean6);

    setState(() {
      for (int i = 0; i < _datas.length; i++) {
        for (int j = 0; j < _list_datas.length; j++) {
          if (_datas[i].year== _list_datas[j].year &&
              _datas[i].month == _list_datas[j].month &&
              _datas[i].day == _list_datas[j].day) {
            _datas[i].work_type = _list_datas[j].work_type;
          }
        }
      }
    });
  }

  //加载日事件,请求接口
  _loadAttendanceDayRecord(String dateTime) async{
    //可根据接口返回的内容在日历下面打卡信息或者其余内容
    print("点击的是$dateTime");
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: customAppbar(//自定义的全局标题
          context: context,
          title: "第二页",
          back: false,
        ),
        body: SingleChildScrollView(
            child: Column(
          children: [
            Container(
              margin: EdgeInsets.all(20.sp),
              decoration: BoxDecoration(
                //设置颜色
                color: MyColors.FFFFFFFF,
                borderRadius: BorderRadius.all(Radius.circular(12.r)),
                //设置四周边框
              ),
              child: Column(
                children: [
                  _yearHeader(),
                  _weekHeader(),
                  _everyDay(),
                ],
              ),
            ),

            //下面可以添加日事件中的信息,例如打卡信息
            Container(),
          ],
        )));
  }

  //头部年
  Widget _yearHeader() {
    return Container(
      height: 30,
      margin: EdgeInsets.only(top: 10),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          GestureDetector(
            onTap: () {
              _lastMonth();
            },
            child: Container(
              margin: EdgeInsets.only(left: 20.sp),
              child: Image(
                width: 24.w,
                height: 24.h,
                image: AssetImage("images/left_icon.webp"),
              ),
            ),
          ),
          Text(
            "$_year 年$_month 月",
            style: TextStyle(fontSize: 16.sp, color: MyColors.FF3C3E43),
          ),
          GestureDetector(
            onTap: () {
              _nextMonth();
            },
            child: Container(
              margin: EdgeInsets.only(right: 20.sp),
              child: Image(
                width: 24.w,
                height: 24.h,
                image: AssetImage("images/right_icon.webp"),
              ),
            ),
          ),
        ],
      ),
    );
  }

  //中部周
  Widget _weekHeader() {
    var array = ["一", "二", "三", "四", "五", "六", "日"];
    return Container(
      height: 20,
      child: GridView.builder(
        padding: EdgeInsets.only(left: 10, right: 10),
        itemCount: array.length,
        shrinkWrap: true,
        physics: NeverScrollableScrollPhysics(),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            //横轴元素个数
            crossAxisCount: 7,
            //纵轴间距
            // mainAxisSpacing: ScreenUtil().setHeight(10),
            // 横轴间距
            // crossAxisSpacing: ScreenUtil().setWidth(15),
            //子组件宽高长度比例
            childAspectRatio: 2),
        itemBuilder: (context, index) {
          return Container(
              alignment: Alignment.center,
              child: Text(
                array[index],
                style: TextStyle(
                    color: index == 5 || index == 6
                        ? MyColors.FFC4C8D0
                        : MyColors.FF3C3E43,
                    fontSize: 13.sp),
              ));
        },
      ),
    );
  }

  //底部日
  Widget _everyDay() {
    return Container(
      child: GridView.builder(
        padding: EdgeInsets.only(left: 10.sp, top: 10.sp, right: 10.sp),
        itemCount: _getRowsForMonthYear(year: _year, month: _month) * 7,
        shrinkWrap: true,
        physics: NeverScrollableScrollPhysics(),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            //横轴元素个数
            crossAxisCount: 7,
            // //纵轴间距
            // mainAxisSpacing: ScreenUtil().setHeight(10),
            // // 横轴间距
            // crossAxisSpacing: ScreenUtil().setWidth(10),
            //子组件宽高长度比例
            childAspectRatio: 1),
        itemBuilder: (context, index) {
          return GestureDetector(
            onTap: () {
              setState(() {
                if (_datas[index].month == _month) {
                  //判断点击的是否是当月日期,只对当前月点击的日期做处理
                  for (int i = 0; i < _datas.length; i++) {
                    if (i == index) {
                      //切换至选中的日期
                      _day = _datas[i].day!;
                      _datas[i].is_select = true;

                      //加载选中的日期事件
                      _loadAttendanceDayRecord( _datas[i].year.toString() + "-" + _datas[i].month.toString()+"-"+ _datas[i].day.toString());
                    } else {
                      _datas[i].is_select = false;
                    }
                  }
                } else {
                  //不是当月的不做处理
                  // _datas[index].is_select=false;
                }
              });
            },
            child: Container(
              child: Column(
                children: [
                  Container(
                    width: 25.sp,
                    height: 25.sp,
                    //设置底部背景
                    decoration: _datas[index].is_select!
                        ? BoxDecoration(
                      color: MyColors.FF2C91F6.withOpacity(0.4),
                      shape: BoxShape.circle,
                    )
                        : BoxDecoration(),
                    child: Center(
                      child: Text(
                        //不是当前月不显示值
                        _datas[index].month == _month
                            ? _datas[index].day.toString()
                            : "",
                        //设置选中字体颜色,以及周末和工作日颜色
                        style: _datas[index].is_select!
                            ? TextStyle(
                                fontSize: 16.sp, color: MyColors.FFFFFFFF)
                            : (index % 7 == 5 || index % 7 == 6
                                ? TextStyle(
                                    fontSize: 16.sp, color: MyColors.FFC4C8D0)
                                : TextStyle(
                                    fontSize: 16.sp, color: MyColors.FF3C3E43)),
                      ),
                    ),
                  ),
                  SizedBox(height: 5),
                  //设置底部小圆点,非当前月的不显示,设置为透明,其余的根据状态判断显示
                  _datas[index].month == _month &&
                          _datas[index].work_type != "" &&
                          _datas[index].work_type != "0"
                      ? Container(
                          height: 6.0,
                          width: 6.0,
                          decoration: BoxDecoration(
                              shape: BoxShape.circle,
                              color: _datas[index].work_type == "1"
                                      ? Color(0xFFF48835)
                                      : Color(0xFF2C91F6)),
                        )
                      : Container(),
                ],
              ),
            ),
          );
        },
      ),
    );
  }

  // 获取行数
  int _getRowsForMonthYear({int? year, int? month}) {
    //当前月天数
    var _currentMonthDays = _getCurrentMonthDays(year: year, month: month);
    //
    var _placeholderDays = _getPlaceholderDays(year: year, month: month);

    int rows = (_currentMonthDays + _placeholderDays) ~/ 7;

    int remainder = (_currentMonthDays + _placeholderDays) % 7;
    if (remainder > 0) {
      rows = rows + 1;
    }
    return rows;
  }

  // 得到这个月的第一天是星期几
  int _getPlaceholderDays({int? year, int? month}) {
    return DateTime(year!, month!).weekday - 1 % 7;
  }

  // 获取当前月份天数
  int _getCurrentMonthDays({int? year, int? month}) {
    if (month == 2) {
      //判断2月份是闰年月还是平年
      if (((year! % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
        return 29;
      } else {
        return 28;
      }
    } else if (month == 1 ||
        month == 3 ||
        month == 5 ||
        month == 7 ||
        month == 8 ||
        month == 10 ||
        month == 12) {
      return 31;
    } else {
      return 30;
    }
  }

  /// 获取展示信息
  _setDatas({int? year, int? month}) {
    /// 上个月占位
    var lastYear = year;
    var lastMonth = month! - 1;
    if (month == 1) {
      lastYear = year! - 1;
      lastMonth = 12;
    }

    var placeholderDays = _getPlaceholderDays(year: year, month: month);
    var lastMonthDays = _getCurrentMonthDays(year: lastYear, month: lastMonth);
    var firstDay = lastMonthDays - placeholderDays;
    for (var i = 0; i < placeholderDays; i++) {
      _datas.add(CalendarModel(
          year: lastYear!,
          month: lastMonth,
          day: firstDay + i + 1,
          is_select: false,
          work_type: ""));
    }

    /// 本月显示
    var currentMonthDays = _getCurrentMonthDays(year: year, month: month);
    for (var i = 0; i < currentMonthDays; i++) {
      if (i == _day - 1) {
        _datas.add(CalendarModel(
            year: year!,
            month: month,
            day: i + 1,
            is_select: true,
            work_type: ""));
      } else {
        _datas.add(CalendarModel(
            year: year!,
            month: month,
            day: i + 1,
            is_select: false,
            work_type: ""));
      }
    }

    /// 下个月占位
    var nextYear = year;
    var nextMonth = month + 1;
    if (month == 12) {
      nextYear = year! + 1;
      nextMonth = 1;
    }
    var nextPlaceholderDays =
        _getPlaceholderDays(year: nextYear, month: nextMonth);
    for (var i = 0; i < 7 - nextPlaceholderDays; i++) {
      _datas.add(CalendarModel(
          year: nextYear!,
          month: nextMonth,
          day: i + 1,
          is_select: false,
          work_type: ""));
    }
  }

  // 上月
  _lastMonth() {
    setState(() {
      if (_month == 1) {
        _year = _year - 1;
        _month = 12;
      } else {
        _month = _month - 1;
      }
      _day = 1; //查看上一个月时,默认选中的为第一天
      _datas.clear();
      _setDatas(year: _year, month: _month);
      //更新月历事件
      _loadAttendanceMonthRecord( _year.toString() + "-" + _month.toString());
      //更新日事件
      _loadAttendanceDayRecord(_year.toString() + "-" + _month.toString()+"-"+_day.toString());
    });
  }

  // 下月
  _nextMonth() {
     if(_month==12){//当前月是12月,下一个月就是下一年
      if(DateTime.now().year>=_year+1){//判断下一年是否大于当前年
        _setNextMonthData();
      }
    }else{//当前月不是12月,还处于当前年
      if(DateTime.now().month>=_month+1){
        //判断下一个月是否超过当前月,超过当前月不做操作
        _setNextMonthData();
      }
    }
  }

     //设置下个月的数据
  _setNextMonthData(){
    setState(() {
      if (_month == 12) {
        _year = _year + 1;
        _month = 1;
      } else {
        _month = _month + 1;
      }
      if (_month == DateTime.now().month) {
        //如果下个月时当前月,默认选中当天
        _day = DateTime.now().day;
      } else {
        //如果不是当前月,默认选中第一天
        _day = 1;
      }
      _datas.clear();
      _setDatas(year: _year, month: _month);
      //更新月历事件
      _loadAttendanceMonthRecord( _year.toString() + "-" + _month.toString());
      //更新日事件
      _loadAttendanceDayRecord(_year.toString() + "-" + _month.toString()+"-"+_day.toString());
    });
  }
}

//日历bean
class CalendarModel {
  int? year;
  int? month;
  int? day;
  String? work_type = "";//日期事件,0,休息,1,异常,2,正常
  bool? is_select = false;

  CalendarModel(
      {this.year, this.month, this.day, this.is_select, this.work_type});
}

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值