birthday_date_picker.dart 5.3 KB

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