birthday_date_picker.dart 6.0 KB

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