import 'package:cs_resources/generated/assets.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:shared/utils/log_utils.dart'; import 'package:widgets/ext/ex_widget.dart'; import 'package:widgets/my_load_image.dart'; //日历的具体展示 class FullCalendar extends StatefulWidget { final DateTime startDate; final DateTime? endDate; final DateTime? selectedDate; final Color? dateColor; final Color? dateSelectedColor; final Color? dateSelectedBg; final double? padding; final String? locale; final Widget? calendarBackground; final List? events; final Function onDateChange; const FullCalendar({ Key? key, this.endDate, required this.startDate, required this.padding, required this.onDateChange, this.calendarBackground, this.events, this.dateColor, this.dateSelectedColor, this.dateSelectedBg, this.locale, this.selectedDate, }) : super(key: key); @override State createState() => _FullCalendarState(); } class _FullCalendarState extends State { late DateTime endDate; late DateTime startDate; late int _initialPage; List? _events = []; late PageController _horizontalScroll; @override void initState() { setState(() { startDate = DateTime.parse("${widget.startDate.toString().split(" ").first} 00:00:00.000"); endDate = DateTime.parse("${widget.endDate.toString().split(" ").first} 23:00:00.000"); _events = widget.events; }); super.initState(); } @override Widget build(BuildContext context) { List partsStart = startDate.toString().split(" ").first.split("-"); DateTime firstDate = DateTime.parse("${partsStart.first}-${partsStart[1].padLeft(2, '0')}-01 00:00:00.000"); List partsEnd = endDate.toString().split(" ").first.split("-"); DateTime lastDate = DateTime.parse("${partsEnd.first}-${(int.parse(partsEnd[1]) + 1).toString().padLeft(2, '0')}-01 23:00:00.000").subtract(const Duration(days: 1)); double width = MediaQuery.of(context).size.width - (2 * widget.padding!); List dates = []; DateTime referenceDate = firstDate; while (referenceDate.isBefore(lastDate)) { List referenceParts = referenceDate.toString().split(" "); DateTime newDate = DateTime.parse("${referenceParts.first} 12:00:00.000"); dates.add(newDate); referenceDate = newDate.add(const Duration(days: 1)); } if (firstDate.year == lastDate.year && firstDate.month == lastDate.month) { return Padding( padding: EdgeInsets.fromLTRB(widget.padding!, 40.0, widget.padding!, 0.0), child: month(dates, width, widget.locale), ); } else { List months = []; for (int i = 0; i < dates.length; i++) { if (i == 0 || (dates[i]!.month != dates[i - 1]!.month)) { months.add(dates[i]); } } months.sort((b, a) => a!.compareTo(b!)); final index = months.indexWhere((element) => element!.month == widget.selectedDate!.month && element.year == widget.selectedDate!.year); _initialPage = index; _horizontalScroll = PageController(initialPage: _initialPage); double monthHeight = 6 * (width / 7) + 16 + 10 + 10 + 80; // 固定高度,6行的高度加上80额外空间 return Container( height: monthHeight, // 使用固定的高度 padding: const EdgeInsets.fromLTRB(25, 10.0, 25, 20.0), //只是PageView child: Stack( children: [ //主题 PageView.builder( physics: const BouncingScrollPhysics(), controller: _horizontalScroll, reverse: true, scrollDirection: Axis.horizontal, itemCount: months.length, itemBuilder: (context, index) { DateTime? date = months[index]; List daysOfMonth = []; for (var item in dates) { if (date!.month == item!.month && date.year == item.year) { daysOfMonth.add(item); } } bool isLast = index == 0; return Container( padding: EdgeInsets.only(bottom: isLast ? 0.0 : 10.0), child: month(daysOfMonth, width, widget.locale), ); }, ), //返回按钮 Positioned( top: -11, width: MediaQuery.of(context).size.width * 0.88, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const MyAssetImage( Assets.baseLibCalendarLeftIcon, width: 44, height: 44, ).onTap(() { _horizontalScroll.nextPage( duration: const Duration(milliseconds: 300), curve: Curves.ease, ); }), const MyAssetImage( Assets.baseLibCalendarRightIcon, width: 44, height: 44, ).onTap(() { _horizontalScroll.previousPage( duration: const Duration(milliseconds: 300), curve: Curves.ease, ); }), ], ), ) ], ), ); } } //顶部星期的文本数据 Widget daysOfWeek(double width, String? locale) { List daysNames = []; for (var day = 12; day <= 18; day++) { daysNames.add(DateFormat.E(locale.toString()).format(DateTime.parse('1970-01-$day'))); } return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ dayName(width / 7, daysNames[0]), dayName(width / 7, daysNames[1]), dayName(width / 7, daysNames[2]), dayName(width / 7, daysNames[3]), dayName(width / 7, daysNames[4]), dayName(width / 7, daysNames[5]), dayName(width / 7, daysNames[6]), ], ); } //顶部星期的文本控件展示 Widget dayName(double width, String text) { return Container( width: width, alignment: Alignment.center, child: Text( text, style: const TextStyle( fontSize: 13.0, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ); } //当前月份,每一天的布局 Widget dateInCalendar(DateTime date, bool outOfRange, double width, bool event) { bool isSelectedDate = date.toString().split(" ").first == widget.selectedDate.toString().split(" ").first; return GestureDetector( onTap: () => outOfRange ? null : widget.onDateChange(date), child: Container( width: width / 7, height: width / 7, decoration: BoxDecoration( shape: BoxShape.circle, color: isSelectedDate ? widget.dateSelectedBg : Colors.transparent, ), alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox( height: 5.0, ), Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Text( DateFormat("dd").format(date), style: TextStyle( color: outOfRange ? isSelectedDate ? widget.dateSelectedColor!.withOpacity(0.9) : widget.dateColor!.withOpacity(0.4) : isSelectedDate ? widget.dateSelectedColor : widget.dateColor, fontWeight: FontWeight.w500, fontSize: 13), ), ), event ? Icon( Icons.bookmark, size: 8, color: isSelectedDate ? widget.dateSelectedColor : widget.dateSelectedBg, ) : const SizedBox(height: 5.0), ], ), ), ); } //单独一个月的Page布局 Widget month(List dates, double width, String? locale) { DateTime first = dates.first; // 获取这个月的第一天 DateTime firstDayOfMonth = DateTime(first.year, first.month, 1); // 找到这个月的第一天是星期几 int firstWeekday = firstDayOfMonth.weekday; // 计算需要的前导空格数量 int leadingDaysCount = (firstWeekday - DateTime.monday + 7) % 7; // 只保留当前月份的日期 List fullDates = List.from(dates); // 在视图中添加用于填充的空日期(如果需要,这里就不填充上个月尾的日期了) for (int i = 0; i < leadingDaysCount; i++) { fullDates.insert(0, null); // 用null填充前导位置 } return Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Text( DateFormat.yMMMM(Locale(locale!).toString()).format(first), style: TextStyle(fontSize: 18.0, color: widget.dateColor, fontWeight: FontWeight.w500), ), // 周一到周天的星期文本 Padding( padding: const EdgeInsets.only(top: 30.0), child: daysOfWeek(width, widget.locale), ), // 底部的每月的每一天 Container( padding: const EdgeInsets.only(top: 10.0), height: (fullDates.length > 28) ? (fullDates.length > 35 ? 6.2 * width / 7 : 5.2 * width / 7) : 4 * width / 7, width: MediaQuery.of(context).size.width - 2 * widget.padding!, child: GridView.builder( itemCount: fullDates.length, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 7), itemBuilder: (context, index) { DateTime? date = fullDates[index]; // 使用 DateTime? 类型以支持 null // 如果 date 为 null,表示该位置为空,返回一个透明的容器 if (date == null) { return Container( width: width / 7, height: width / 7, color: Colors.transparent, // 透明或其他样式 ); } bool outOfRange = date.isBefore(startDate) || date.isAfter(endDate); return dateInCalendar( date, outOfRange, width, _events!.contains(date.toString().split(" ").first) && !outOfRange, ); }, ), ) ], ); } }