Flutter 阴历选择器 lunar

效果如下


image.png

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()}');
                },
              );
            },);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容