birthday_date_picker.dart 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. const BirthdayDatePicker({
  10. super.key,
  11. required this.initialDate,
  12. required this.minimumDate,
  13. required this.maximumDate,
  14. required this.onDateChanged,
  15. });
  16. @override
  17. State<BirthdayDatePicker> createState() => _BirthdayDatePickerState();
  18. }
  19. class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
  20. late int selectedYear;
  21. late int selectedMonth;
  22. late int selectedDay;
  23. @override
  24. void initState() {
  25. super.initState();
  26. selectedYear = widget.initialDate.year;
  27. selectedMonth = widget.initialDate.month;
  28. selectedDay = widget.initialDate.day;
  29. }
  30. @override
  31. Widget build(BuildContext context) {
  32. return Container(
  33. height: 150.h,
  34. child: Stack(
  35. children: [
  36. Positioned(
  37. bottom: 57.h, // Position at the bottom
  38. left: 0,
  39. right: 0,
  40. top: 57.h,
  41. child: Container(
  42. height: 36.h,
  43. decoration: BoxDecoration(
  44. color: Colors.white.withValues(alpha: 0.6), // Background color
  45. borderRadius: BorderRadius.circular(14.r),
  46. ),
  47. ),
  48. ),
  49. Row(
  50. mainAxisAlignment: MainAxisAlignment.center,
  51. children: [
  52. _buildPickerBuilder(
  53. count: widget.maximumDate.year - widget.minimumDate.year + 1,
  54. initialIndex: selectedYear - widget.minimumDate.year,
  55. itemBuilder: (index) => '${widget.minimumDate.year + index}年',
  56. onSelectedItemChanged: (index) {
  57. setState(() {
  58. selectedYear = widget.minimumDate.year + index;
  59. _adjustMonthAndDay();
  60. _notify();
  61. });
  62. },
  63. ),
  64. _buildPickerBuilder(
  65. count: _getMaxMonth(),
  66. initialIndex: selectedMonth - 1,
  67. itemBuilder: (index) => '${index + 1}月',
  68. onSelectedItemChanged: (index) {
  69. setState(() {
  70. selectedMonth = index + 1;
  71. _adjustDayForMonth();
  72. _notify();
  73. });
  74. },
  75. ),
  76. _buildPickerBuilder(
  77. count: _getMaxDay(),
  78. initialIndex: selectedDay - 1,
  79. itemBuilder: (index) => '${index + 1}日',
  80. onSelectedItemChanged: (index) {
  81. setState(() {
  82. selectedDay = index + 1;
  83. _notify();
  84. });
  85. },
  86. ),
  87. ],
  88. ),
  89. // Positioned Stack item (will be at the bottom of the Row)
  90. ],
  91. ),
  92. );
  93. }
  94. Widget _buildPickerBuilder({
  95. required int count,
  96. required int initialIndex,
  97. required String Function(int) itemBuilder,
  98. required Function(int) onSelectedItemChanged,
  99. }) {
  100. return Expanded(
  101. child: ListWheelScrollView.useDelegate(
  102. controller: FixedExtentScrollController(initialItem: initialIndex),
  103. itemExtent: 36.h,
  104. onSelectedItemChanged: onSelectedItemChanged,
  105. overAndUnderCenterOpacity: 0.35,
  106. childDelegate: ListWheelChildBuilderDelegate(
  107. builder: (context, index) {
  108. // 判断选中项
  109. bool isSelected = index == initialIndex;
  110. return Container(
  111. alignment: Alignment.center,
  112. color: Colors.transparent,
  113. child: Text(
  114. itemBuilder(index),
  115. style: TextStyle(
  116. fontSize: 16.sp,
  117. fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
  118. color: isSelected ? Colors.black : Colors.black,
  119. ),
  120. ),
  121. );
  122. },
  123. childCount: count,
  124. ),
  125. ),
  126. );
  127. }
  128. void _adjustMonthAndDay() {
  129. if (selectedYear == widget.maximumDate.year &&
  130. selectedMonth > widget.maximumDate.month) {
  131. selectedMonth = widget.maximumDate.month;
  132. }
  133. _adjustDayForMonth();
  134. }
  135. void _adjustDayForMonth() {
  136. int maxDay = _getMaxDay();
  137. if (selectedDay > maxDay) {
  138. selectedDay = maxDay;
  139. }
  140. }
  141. int _getMaxMonth() {
  142. if (selectedYear == widget.maximumDate.year) {
  143. return widget.maximumDate.month;
  144. }
  145. return 12;
  146. }
  147. int _getMaxDay() {
  148. if (selectedYear == widget.maximumDate.year &&
  149. selectedMonth == widget.maximumDate.month) {
  150. return widget.maximumDate.day;
  151. }
  152. return _getDaysInMonth(selectedYear, selectedMonth);
  153. }
  154. int _getDaysInMonth(int year, int month) {
  155. switch (month) {
  156. case 2:
  157. if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
  158. return 29;
  159. }
  160. return 28;
  161. case 4:
  162. case 6:
  163. case 9:
  164. case 11:
  165. return 30;
  166. default:
  167. return 31;
  168. }
  169. }
  170. void _notify() {
  171. final selectedDate = DateTime(selectedYear, selectedMonth, selectedDay);
  172. widget.onDateChanged(selectedDate);
  173. }
  174. }