|
|
@@ -7,7 +7,6 @@ class BirthdayDatePicker extends StatefulWidget {
|
|
|
final DateTime minimumDate;
|
|
|
final DateTime maximumDate;
|
|
|
final void Function(DateTime) onDateChanged;
|
|
|
- // 中间背景色
|
|
|
final Color backgroundColor;
|
|
|
|
|
|
const BirthdayDatePicker({
|
|
|
@@ -28,12 +27,32 @@ class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
|
|
|
late int selectedMonth;
|
|
|
late int selectedDay;
|
|
|
|
|
|
+ // 吸附效果控制器
|
|
|
+ final List<FixedExtentScrollController> _controllers = [
|
|
|
+ FixedExtentScrollController(),
|
|
|
+ FixedExtentScrollController(),
|
|
|
+ FixedExtentScrollController(),
|
|
|
+ ];
|
|
|
+
|
|
|
@override
|
|
|
void initState() {
|
|
|
super.initState();
|
|
|
selectedYear = widget.initialDate.year;
|
|
|
selectedMonth = widget.initialDate.month;
|
|
|
selectedDay = widget.initialDate.day;
|
|
|
+
|
|
|
+ // 初始化控制器位置
|
|
|
+ WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
+ _controllers[0].jumpToItem(selectedYear - widget.minimumDate.year);
|
|
|
+ _controllers[1].jumpToItem(selectedMonth - 1);
|
|
|
+ _controllers[2].jumpToItem(selectedDay - 1);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void dispose() {
|
|
|
+ _controllers.forEach((controller) => controller.dispose());
|
|
|
+ super.dispose();
|
|
|
}
|
|
|
|
|
|
@override
|
|
|
@@ -43,14 +62,14 @@ class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
|
|
|
child: Stack(
|
|
|
children: [
|
|
|
Positioned(
|
|
|
- bottom: 57.h, // Position at the bottom
|
|
|
+ bottom: 57.h,
|
|
|
left: 0,
|
|
|
right: 0,
|
|
|
top: 57.h,
|
|
|
child: Container(
|
|
|
height: 36.h,
|
|
|
decoration: BoxDecoration(
|
|
|
- color: widget.backgroundColor, // Background color
|
|
|
+ color: widget.backgroundColor,
|
|
|
borderRadius: BorderRadius.circular(14.r),
|
|
|
),
|
|
|
),
|
|
|
@@ -58,51 +77,66 @@ class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
|
|
|
Row(
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
children: [
|
|
|
- _buildPickerBuilder(
|
|
|
- count: widget.maximumDate.year - widget.minimumDate.year + 1,
|
|
|
- initialIndex: selectedYear - widget.minimumDate.year,
|
|
|
- itemBuilder: (index) => '${widget.minimumDate.year + index}年',
|
|
|
- onSelectedItemChanged: (index) {
|
|
|
- setState(() {
|
|
|
- selectedYear = widget.minimumDate.year + index;
|
|
|
- _adjustMonthAndDay();
|
|
|
- _notify();
|
|
|
- });
|
|
|
- },
|
|
|
- ),
|
|
|
- _buildPickerBuilder(
|
|
|
- count: _getMaxMonth(),
|
|
|
- initialIndex: selectedMonth - 1,
|
|
|
- itemBuilder: (index) => '${index + 1}月',
|
|
|
- onSelectedItemChanged: (index) {
|
|
|
- setState(() {
|
|
|
- selectedMonth = index + 1;
|
|
|
- _adjustDayForMonth();
|
|
|
- _notify();
|
|
|
- });
|
|
|
- },
|
|
|
- ),
|
|
|
- _buildPickerBuilder(
|
|
|
- count: _getMaxDay(),
|
|
|
- initialIndex: selectedDay - 1,
|
|
|
- itemBuilder: (index) => '${index + 1}日',
|
|
|
- onSelectedItemChanged: (index) {
|
|
|
- setState(() {
|
|
|
- selectedDay = index + 1;
|
|
|
- _notify();
|
|
|
- });
|
|
|
- },
|
|
|
- ),
|
|
|
+ _buildYearPicker(),
|
|
|
+ _buildMonthPicker(),
|
|
|
+ _buildDayPicker(),
|
|
|
],
|
|
|
),
|
|
|
-
|
|
|
- // Positioned Stack item (will be at the bottom of the Row)
|
|
|
],
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ Widget _buildYearPicker() {
|
|
|
+ final yearCount = widget.maximumDate.year - widget.minimumDate.year + 1;
|
|
|
+ return _buildPickerBuilder(
|
|
|
+ controller: _controllers[0],
|
|
|
+ count: yearCount,
|
|
|
+ initialIndex: selectedYear - widget.minimumDate.year,
|
|
|
+ itemBuilder: (index) => '${widget.minimumDate.year + index}年',
|
|
|
+ onSelectedItemChanged: (index) {
|
|
|
+ setState(() {
|
|
|
+ selectedYear = widget.minimumDate.year + index;
|
|
|
+ _adjustMonthAndDay();
|
|
|
+ _notify();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildMonthPicker() {
|
|
|
+ return _buildPickerBuilder(
|
|
|
+ controller: _controllers[1],
|
|
|
+ count: _getMaxMonth(),
|
|
|
+ initialIndex: selectedMonth - 1,
|
|
|
+ itemBuilder: (index) => '${index + 1}月',
|
|
|
+ onSelectedItemChanged: (index) {
|
|
|
+ setState(() {
|
|
|
+ selectedMonth = index + 1;
|
|
|
+ _adjustDayForMonth();
|
|
|
+ _notify();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildDayPicker() {
|
|
|
+ return _buildPickerBuilder(
|
|
|
+ controller: _controllers[2],
|
|
|
+ count: _getMaxDay(),
|
|
|
+ initialIndex: selectedDay - 1,
|
|
|
+ itemBuilder: (index) => '${index + 1}日',
|
|
|
+ onSelectedItemChanged: (index) {
|
|
|
+ setState(() {
|
|
|
+ selectedDay = index + 1;
|
|
|
+ _notify();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
Widget _buildPickerBuilder({
|
|
|
+ required FixedExtentScrollController controller,
|
|
|
required int count,
|
|
|
required int initialIndex,
|
|
|
required String Function(int) itemBuilder,
|
|
|
@@ -110,23 +144,22 @@ class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
|
|
|
}) {
|
|
|
return Expanded(
|
|
|
child: ListWheelScrollView.useDelegate(
|
|
|
- controller: FixedExtentScrollController(initialItem: initialIndex),
|
|
|
+ controller: controller,
|
|
|
itemExtent: 36.h,
|
|
|
onSelectedItemChanged: onSelectedItemChanged,
|
|
|
- overAndUnderCenterOpacity: 0.35,
|
|
|
+ physics: FixedExtentScrollPhysics(),
|
|
|
+ overAndUnderCenterOpacity: 0.35,
|
|
|
childDelegate: ListWheelChildBuilderDelegate(
|
|
|
builder: (context, index) {
|
|
|
- // 判断选中项
|
|
|
- bool isSelected = index == initialIndex;
|
|
|
+ final isSelected = controller.selectedItem == index;
|
|
|
return Container(
|
|
|
alignment: Alignment.center,
|
|
|
- color: Colors.transparent,
|
|
|
child: Text(
|
|
|
itemBuilder(index),
|
|
|
style: TextStyle(
|
|
|
fontSize: 16.sp,
|
|
|
-
|
|
|
color: isSelected ? Colors.black : Colors.black,
|
|
|
+ fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
|
|
),
|
|
|
),
|
|
|
);
|
|
|
@@ -137,9 +170,9 @@ class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ // 自动调整月和日
|
|
|
void _adjustMonthAndDay() {
|
|
|
- if (selectedYear == widget.maximumDate.year &&
|
|
|
- selectedMonth > widget.maximumDate.month) {
|
|
|
+ if (selectedYear == widget.maximumDate.year && selectedMonth > widget.maximumDate.month) {
|
|
|
selectedMonth = widget.maximumDate.month;
|
|
|
}
|
|
|
_adjustDayForMonth();
|
|
|
@@ -160,8 +193,7 @@ class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
|
|
|
}
|
|
|
|
|
|
int _getMaxDay() {
|
|
|
- if (selectedYear == widget.maximumDate.year &&
|
|
|
- selectedMonth == widget.maximumDate.month) {
|
|
|
+ if (selectedYear == widget.maximumDate.year && selectedMonth == widget.maximumDate.month) {
|
|
|
return widget.maximumDate.day;
|
|
|
}
|
|
|
return _getDaysInMonth(selectedYear, selectedMonth);
|
|
|
@@ -184,6 +216,7 @@ class _BirthdayDatePickerState extends State<BirthdayDatePicker> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 回调函数通知外部
|
|
|
void _notify() {
|
|
|
final selectedDate = DateTime(selectedYear, selectedMonth, selectedDay);
|
|
|
widget.onDateChanged(selectedDate);
|