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()}');
                },
              );
            },);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353