birthday_date_picker.dart 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_screenutil/flutter_screenutil.dart';
  4. class BirthdayDatePicker extends StatefulWidget {
  5. final DateTime initialDate;
  6. final DateTime minimumDate;
  7. final DateTime maximumDate;
  8. final void Function(DateTime) onDateChanged;
  9. final Color backgroundColor;
  10. const BirthdayDatePicker({
  11. super.key,
  12. required this.initialDate,
  13. required this.minimumDate,
  14. required this.maximumDate,
  15. required this.onDateChanged,
  16. this.backgroundColor = const Color.fromRGBO(255, 255, 255, 0.6),
  17. });
  18. @override
  19. State<BirthdayDatePicker> createState() => _BirthdayDatePickerState();
  20. }
  21. class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
  22. late int selectedYear;
  23. late int selectedMonth;
  24. late int selectedDay;
  25. // 吸附效果控制器
  26. final List<FixedExtentScrollController> _controllers = [
  27. FixedExtentScrollController(),
  28. FixedExtentScrollController(),
  29. FixedExtentScrollController(),
  30. ];
  31. @override
  32. void initState() {
  33. super.initState();
  34. selectedYear = widget.initialDate.year;
  35. selectedMonth = widget.initialDate.month;
  36. selectedDay = widget.initialDate.day;
  37. // 初始化控制器位置
  38. WidgetsBinding.instance.addPostFrameCallback((_) {
  39. _controllers[0].jumpToItem(selectedYear - widget.minimumDate.year);
  40. _controllers[1].jumpToItem(selectedMonth - 1);
  41. _controllers[2].jumpToItem(selectedDay - 1);
  42. });
  43. }
  44. @override
  45. void dispose() {
  46. _controllers.forEach((controller) => controller.dispose());
  47. super.dispose();
  48. }
  49. @override
  50. Widget build(BuildContext context) {
  51. return Container(
  52. height: 150.h,
  53. child: Stack(
  54. children: [
  55. Positioned(
  56. bottom: 57.h,
  57. left: 0,
  58. right: 0,
  59. top: 57.h,
  60. child: Container(
  61. height: 36.h,
  62. decoration: BoxDecoration(
  63. color: widget.backgroundColor,
  64. borderRadius: BorderRadius.circular(14.r),
  65. ),
  66. ),
  67. ),
  68. Row(
  69. mainAxisAlignment: MainAxisAlignment.center,
  70. children: [
  71. _buildYearPicker(),
  72. _buildMonthPicker(),
  73. _buildDayPicker(),
  74. ],
  75. ),
  76. ],
  77. ),
  78. );
  79. }
  80. Widget _buildYearPicker() {
  81. final yearCount = widget.maximumDate.year - widget.minimumDate.year + 1;
  82. return _buildPickerBuilder(
  83. controller: _controllers[0],
  84. count: yearCount,
  85. initialIndex: selectedYear - widget.minimumDate.year,
  86. itemBuilder: (index) => '${widget.minimumDate.year + index}年',
  87. onSelectedItemChanged: (index) {
  88. setState(() {
  89. selectedYear = widget.minimumDate.year + index;
  90. _adjustMonthAndDay();
  91. _notify();
  92. });
  93. },
  94. );
  95. }
  96. Widget _buildMonthPicker() {
  97. return _buildPickerBuilder(
  98. controller: _controllers[1],
  99. count: _getMaxMonth(),
  100. initialIndex: selectedMonth - 1,
  101. itemBuilder: (index) => '${index + 1}月',
  102. onSelectedItemChanged: (index) {
  103. setState(() {
  104. selectedMonth = index + 1;
  105. _adjustDayForMonth();
  106. _notify();
  107. });
  108. },
  109. );
  110. }
  111. Widget _buildDayPicker() {
  112. return _buildPickerBuilder(
  113. controller: _controllers[2],
  114. count: _getMaxDay(),
  115. initialIndex: selectedDay - 1,
  116. itemBuilder: (index) => '${index + 1}日',
  117. onSelectedItemChanged: (index) {
  118. setState(() {
  119. selectedDay = index + 1;
  120. _notify();
  121. });
  122. },
  123. );
  124. }
  125. Widget _buildPickerBuilder({
  126. required FixedExtentScrollController controller,
  127. required int count,
  128. required int initialIndex,
  129. required String Function(int) itemBuilder,
  130. required Function(int) onSelectedItemChanged,
  131. }) {
  132. return Expanded(
  133. child: ListWheelScrollView.useDelegate(
  134. controller: controller,
  135. itemExtent: 36.h,
  136. onSelectedItemChanged: onSelectedItemChanged,
  137. physics: FixedExtentScrollPhysics(),
  138. overAndUnderCenterOpacity: 0.35,
  139. childDelegate: ListWheelChildBuilderDelegate(
  140. builder: (context, index) {
  141. final isSelected = controller.selectedItem == index;
  142. return Container(
  143. alignment: Alignment.center,
  144. child: Text(
  145. itemBuilder(index),
  146. style: TextStyle(
  147. fontSize: 16.sp,
  148. color: isSelected ? Colors.black : Colors.black,
  149. fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
  150. ),
  151. ),
  152. );
  153. },
  154. childCount: count,
  155. ),
  156. ),
  157. );
  158. }
  159. // 自动调整月和日
  160. void _adjustMonthAndDay() {
  161. if (selectedYear == widget.maximumDate.year && selectedMonth > widget.maximumDate.month) {
  162. selectedMonth = widget.maximumDate.month;
  163. }
  164. _adjustDayForMonth();
  165. }
  166. void _adjustDayForMonth() {
  167. int maxDay = _getMaxDay();
  168. if (selectedDay > maxDay) {
  169. selectedDay = maxDay;
  170. }
  171. }
  172. int _getMaxMonth() {
  173. if (selectedYear == widget.maximumDate.year) {
  174. return widget.maximumDate.month;
  175. }
  176. return 12;
  177. }
  178. int _getMaxDay() {
  179. if (selectedYear == widget.maximumDate.year && selectedMonth == widget.maximumDate.month) {
  180. return widget.maximumDate.day;
  181. }
  182. return _getDaysInMonth(selectedYear, selectedMonth);
  183. }
  184. int _getDaysInMonth(int year, int month) {
  185. switch (month) {
  186. case 2:
  187. if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
  188. return 29;
  189. }
  190. return 28;
  191. case 4:
  192. case 6:
  193. case 9:
  194. case 11:
  195. return 30;
  196. default:
  197. return 31;
  198. }
  199. }
  200. // 回调函数通知外部
  201. void _notify() {
  202. final selectedDate = DateTime(selectedYear, selectedMonth, selectedDay);
  203. widget.onDateChanged(selectedDate);
  204. }
  205. }