本文最后更新于 631 天前,其中的信息可能已经有所发展或是发生改变。
import 'package:flutter/material.dart';
class CalendarWidget extends StatefulWidget {
// 选择器范围
DateTime startTime;
DateTime endTime;
// 日期文本字体大小
TextStyle? style;
// 头部 周文本样式
TextStyle? headerStyle;
// 日期容器大小
double? size;
// 选择器高度
double? height;
// 选择器padding
EdgeInsets? padding;
// 选中回调
Function(DateTime dateTime)? onTapTime;
CalendarWidget({
Key? key,
required this.startTime,
required this.endTime,
this.style,
this.size,
this.height,
this.padding,
this.onTapTime,
this.headerStyle,
}) : super(key: key);
@override
State<CalendarWidget> createState() => _CalendarWidgetState();
}
class _CalendarWidgetState extends State<CalendarWidget> {
/*
{
end:"2024-12-01", //最后可显示日历的日期
hot:{"2023-07-05":"2023-08-31","2024-07-05":"2024-08-31"}, //所有暑假段
code:{"2023-01-15":"2023-02-06","2024-01-15":"2024-02-06"}, //所有寒假段
extholiday:["2023-05-03","2023-05-04"], //额外假日列表
extworkday:["2023-06-08","2023-06-09"] //额外工作日列表
}
*/
late PageController _pageController;
List<WeekItem> source = [];
late DateTime start;
late DateTime end;
// 记录之前页码 用于清除之前页选中效果
int beforeChoosePageIndex = -1;
@override
void initState() {
DateTime currentDate = DateTime.now();
start = widget.startTime;
end = widget.endTime;
// 初始化构建三页数据
List<ItemConfig> weekDates = getWeekDates(currentDate);
source.add(WeekItem()..week = weekDates);
source.insert(0, WeekItem()..week = getPreviousWeek(weekDates[0].dateTime));
source.add(WeekItem()..week = getNextWeek(weekDates[6].dateTime));
_pageController = PageController(initialPage: 1);
beforeChoosePageIndex = 1;
super.initState();
}
@override
void dispose() {
super.dispose();
_pageController.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 15,vertical: 15),
child: Column(
children: [
Row(
children: List.generate(7, (index) => Expanded(
child: Text(_getDayText(index),textAlign: TextAlign.center,style: widget.headerStyle??const TextStyle(
fontSize: 12,
color: Color(0xFF999999)
),),
)),
),
Padding(
padding: widget.padding??const EdgeInsets.only(top: 12.0),
child: SizedBox(
height: widget.height??36,
child: PageView.builder(
controller: _pageController,
onPageChanged: (page) {
if(page == 0) {
setState(() {
source.insert(0,WeekItem()..week = getPreviousWeek(source[0].week[0].dateTime));
_pageController.jumpTo(_pageController.position.pixels + _pageController.position.viewportDimension);
});
} else if(page == source.length - 1) {
setState(() {
source.add(WeekItem()..week = getNextWeek(source[source.length - 1].week[6].dateTime));
});
}
},
itemBuilder: (BuildContext context, int index) {
WeekItem weekItem = source[index];
return Row(
children: List.generate(7, (index) {
DateTime now = weekItem.week[index].dateTime;
// 是否超出startTime 是否超出endTime
bool isEnable = now.isBefore(start) || end.isBefore(now);
bool isToday = _isSameDate(now,DateTime.now());
bool isChoose = weekItem.chooseIndex == index;
String extraTip = weekItem.week[index].extraTip;
Widget dateTimeWidget = Text("${now.day}",style: widget.style??TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
color: isChoose ? Colors.white : const Color(0xFF666666)
));
return Expanded(
child: UnconstrainedBox(
child: GestureDetector(
onTap: () {
// 重置之前选中状态
if(beforeChoosePageIndex != _pageController.page && beforeChoosePageIndex >= 0 && beforeChoosePageIndex < source.length) {
source[beforeChoosePageIndex].chooseIndex = -1;
beforeChoosePageIndex = toInt(_pageController.page);
}
widget.onTapTime?.call(weekItem.week[index].dateTime);
if(mounted) {
setState(() {
weekItem.chooseIndex = index;
});
}
},
child: Container(
width: widget.size??36,
height: widget.size??36,
decoration: BoxDecoration(
color: isEnable ? weekItem.disableColor : (
isChoose ? weekItem.chooseColor : (isToday ? weekItem.todayColor : Colors.transparent)),
borderRadius: BorderRadius.circular(widget.size == null ? 18 : widget.size! / 2)
),
child: extraTip.isEmpty || isChoose ? Center(
child: dateTimeWidget,
) : Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
dateTimeWidget,
Text(extraTip,style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 11,
color: Color(0xFFFF893F)
)),
],
),
),
),
),
);
}),
);
},
itemCount: source.length,
),
),
)
],
),
);
}
int toInt(double? page) {
if(page == null) {
return -1;
}
try {
return page.toInt();
} catch(e) {
return -1;
}
}
bool _isSameDate(DateTime date1, DateTime date2) {
return date1.year == date2.year && date1.month == date2.month && date1.day == date2.day;
}
String _getDayText(int index) {
switch(index) {
case 0 :
return "一";
case 1 :
return "二";
case 2 :
return "三";
case 3 :
return "四";
case 4 :
return "五";
case 5 :
return "六";
case 6 :
return "七";
}
return "一";
}
List<ItemConfig> getWeekDates(DateTime dateTime) {
List<ItemConfig> weekDates = [];
DateTime firstDayOfWeek = dateTime.subtract(Duration(days: dateTime.weekday - 1));
for (int i = 0; i < 7; i++) {
DateTime now = firstDayOfWeek.add(Duration(days: i));
// 测试使用
String extraTip = "";
if(7 <= now.month && now.month <= 9) {
extraTip = "暑";
}
if(1 <= now.month && now.month <= 2) {
extraTip = "寒";
}
weekDates.add(ItemConfig()..dateTime = now..extraTip = extraTip);
}
return weekDates;
}
List<ItemConfig> getPreviousWeek(DateTime dateTime) {
DateTime startDate = dateTime.subtract(const Duration(days: 7));
return getWeekDates(startDate);
}
List<ItemConfig> getNextWeek(DateTime dateTime) {
DateTime endDate = dateTime.add(const Duration(days: 1));
return getWeekDates(endDate);
}
}
class WeekItem {
Color todayColor = const Color(0xFF0BB9B9).withOpacity(0.1);
Color disableColor = const Color(0xFFE5ECF3);
Color chooseColor = const Color(0xFF0BB9B9);
List<ItemConfig> week = [];
// 点击索引
int chooseIndex = -1;
}
class ItemConfig {
DateTime dateTime = DateTime.now();
String extraTip = "";
@override
String toString() {
return 'ItemConfig{dateTime: $dateTime, extraTip: $extraTip}';
}
}