效果如下
pubspec.yaml
导入包
lunar: ^1.7.1
lunar_date_picker.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:lunar/lunar.dart';
class LunarDatePicker extends StatefulWidget {
final Function(Lunar date)? onDateSelected;
const LunarDatePicker({
super.key,
this.onDateSelected,
});
@override
State<LunarDatePicker> createState() => _LunarDatePickerState();
}
class _LunarDatePickerState extends State<LunarDatePicker> {
Lunar selectedDate = Lunar.fromDate(DateTime.now());
late LunarMonth selectedYm;
final List<int> years = List.generate(11, (index) => 2020 + index);
late List<int> months;
late List<int> days;
// 选择器的列数
static const int _yearColumn = 0;
static const int _monthColumn = 1;
static const int _dayColumn = 2;
// 添加农历月份表示
final List<String> lunarMonths = [
'正月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '冬月', '腊月'
];
// 添加农历日期表示
final List<String> lunarDays = [
'初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
'廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十'
];
@override
void initState() {
super.initState();
_updateMonths();
_updateDays();
}
void _updateMonths() {
// 获取当年的月份列表,包括闰月
LunarYear lunarYear = LunarYear.fromYear(selectedDate.getYear());
months = [];
for (int i = 1; i <= 12; i++) {
months.add(i);
if (lunarYear.getLeapMonth() == i) {
// 在闰月位置后添加负数表示闰月
months.add(-i);
}
}
}
void _updateDays() {
// 使用 LunarMonth.fromYm 获取指定年月的农历月
LunarMonth? lunarMonth = LunarMonth.fromYm(selectedDate.getYear(), selectedDate.getMonth());
// 获取该月的天数
int dayCount = lunarMonth?.getDayCount() ?? 30;
days = List.generate(dayCount, (index) => index + 1);
}
String _getChineseYear(int year) {
Lunar lunar = Lunar.fromYmd(year, 1, 1);
return '$year ${lunar.getYearInGanZhi()}';
}
String _getChineseMonth(int month) {
if (month < 0) {
// 闰月
return '闰${lunarMonths[(-month - 1)]}';
}
return lunarMonths[month - 1];
}
void _handleDateSelected() {
Navigator.of(context).pop();
if (widget.onDateSelected != null) {
widget.onDateSelected!(selectedDate);
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(15),topRight: Radius.circular(15)),
color: Colors.white
),
height: 300,
child: Column(
children: [
// 顶部工具栏
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: CupertinoColors.separator.withOpacity(0.5),
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
padding: EdgeInsets.zero,
child: const Text('取消'),
onPressed: () => Navigator.of(context).pop(),
),
const Text(
'选择目标日',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
),
),
CupertinoButton(
padding: EdgeInsets.zero,
onPressed: _handleDateSelected,
child: const Text('确定'),
),
],
),
),
// 选择器部分
Expanded(
child: CupertinoPickerBuilder(
backgroundColor: Colors.white,
itemExtent: 44,
magnification: 1.22,
squeeze: 1.2,
useMagnifier: true,
onSelectedItemChanged: (column, index) {
setState(() {
switch (column) {
case _yearColumn:
int newYear = years[index];
// 更新年份时需要检查月份是否合法(比如闰月是否存在)
LunarYear lunarYear = LunarYear.fromYear(newYear);
int currentMonth = selectedDate.getMonth();
if (currentMonth < 0) {
// 如果当前是闰月,检查新年份是否有该闰月
if (lunarYear.getLeapMonth() != -currentMonth) {
// 如果新年份没有这个闰月,则使用普通月
currentMonth = -currentMonth;
}
}
selectedDate = Lunar.fromYmd(
newYear,
currentMonth,
days[index],
);
_updateMonths();
_updateDays();
break;
case _monthColumn:
int newMonth = months[index];
selectedDate = Lunar.fromYmd(
selectedDate.getYear(),
newMonth,
days[index],
);
// print("month>>>> ${selectedDate.getMonth()}");
_updateDays();
break;
case _dayColumn:
selectedDate = Lunar.fromYmd(
selectedDate.getYear(),
selectedDate.getMonth(),
days[index],
);
break;
}
});
},
columnBuilder: (context, column) {
switch (column) {
case _yearColumn:
return PickerColumn(
items: years.map((year) {
return Container(
alignment: Alignment.center,
child: Text(
_getChineseYear(year),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w400,
),
),
);
}).toList(),
initialItem: years.indexOf(selectedDate.getYear()),
);
case _monthColumn:
return PickerColumn(
items: months.map((month) {
return Container(
alignment: Alignment.center,
child: Text(
_getChineseMonth(month),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w400,
),
),
);
}).toList(),
initialItem: months.indexOf(selectedDate.getMonth()),
);
case _dayColumn:
return PickerColumn(
items: days.map((day) {
return Container(
alignment: Alignment.center,
child: Text(
lunarDays[day - 1],
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w400,
),
),
);
}).toList(),
initialItem: selectedDate.getDay() - 1,
);
default:
throw Exception('Invalid column: $column');
}
},
columnCount: 3,
),
),
],
),
);
}
}
class CupertinoPickerBuilder extends StatelessWidget {
final double itemExtent;
final Widget? selectionOverlay;
final double magnification;
final double squeeze;
final Color backgroundColor;
final bool useMagnifier;
final Function(int column, int index)? onSelectedItemChanged;
final int columnCount;
final PickerColumn Function(BuildContext context, int column) columnBuilder;
const CupertinoPickerBuilder({
super.key,
required this.itemExtent,
this.selectionOverlay,
this.magnification = 1.0,
this.squeeze = 1.45,
this.backgroundColor = Colors.white,
this.useMagnifier = false,
this.onSelectedItemChanged,
required this.columnCount,
required this.columnBuilder,
});
@override
Widget build(BuildContext context) {
return Row(
children: List.generate(columnCount, (column) {
final pickerColumn = columnBuilder(context, column);
return Expanded(
child: CupertinoPicker(
itemExtent: itemExtent,
selectionOverlay: selectionOverlay,
magnification: magnification,
squeeze: squeeze,
backgroundColor: backgroundColor,
useMagnifier: useMagnifier,
scrollController: FixedExtentScrollController(
initialItem: pickerColumn.initialItem,
),
onSelectedItemChanged: (index) {
onSelectedItemChanged?.call(column, index);
},
children: pickerColumn.items,
),
);
}),
);
}
}
class PickerColumn {
final List<Widget> items;
final int initialItem;
const PickerColumn({
required this.items,
required this.initialItem,
});
}
如何使用
showModalBottomSheet(context: context, builder: (context) {
return LunarDatePicker(
onDateSelected: (date) {
print(
'农历日期:${date.getYear()} ${date.getMonth()} ${date.getDay()}');
print('阳历日期:${date.getSolar().toFullString()}');
},
);
},);