date_picker.dart 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import 'package:flutter/material.dart';
  2. import 'package:intl/intl.dart';
  3. /// 通用日期选择器组件
  4. /// 支持功能:
  5. /// - 初始日期设置
  6. /// - 日期范围限制
  7. /// - 自定义日期格式
  8. /// - 主题适配
  9. /// - 空值处理
  10. /// - 验证支持
  11. class DatePickerField extends StatefulWidget {
  12. final DateTime? initialDate;
  13. final DateTime? firstDate;
  14. final DateTime? lastDate;
  15. final ValueChanged<DateTime?> onDateSelected;
  16. final String labelText;
  17. final String? hintText;
  18. final bool isRequired;
  19. final TextStyle? textStyle;
  20. final InputDecoration? decoration;
  21. final DateFormat? dateFormat;
  22. final bool allowClear;
  23. final bool autoValidate;
  24. const DatePickerField({
  25. super.key,
  26. this.initialDate,
  27. this.firstDate,
  28. this.lastDate,
  29. required this.onDateSelected,
  30. this.labelText = '选择日期',
  31. this.hintText,
  32. this.isRequired = false,
  33. this.textStyle,
  34. this.decoration,
  35. this.dateFormat,
  36. this.allowClear = true,
  37. this.autoValidate = false,
  38. });
  39. @override
  40. State<DatePickerField> createState() => _DatePickerFieldState();
  41. }
  42. class _DatePickerFieldState extends State<DatePickerField> {
  43. late final TextEditingController _controller;
  44. DateTime? _selectedDate;
  45. final DateFormat _defaultFormat = DateFormat('yyyy-MM-dd');
  46. @override
  47. void initState() {
  48. super.initState();
  49. _selectedDate = widget.initialDate;
  50. _controller = TextEditingController(
  51. text: _selectedDate != null ? _getFormattedDate(_selectedDate!) : '',
  52. );
  53. }
  54. @override
  55. void didUpdateWidget(covariant DatePickerField oldWidget) {
  56. if (widget.initialDate != oldWidget.initialDate) {
  57. _updateDate(widget.initialDate);
  58. }
  59. super.didUpdateWidget(oldWidget);
  60. }
  61. String _getFormattedDate(DateTime date) {
  62. return (widget.dateFormat ?? _defaultFormat).format(date);
  63. }
  64. void _updateDate(DateTime? newDate) {
  65. setState(() {
  66. _selectedDate = newDate;
  67. _controller.text = newDate != null ? _getFormattedDate(newDate) : '';
  68. });
  69. widget.onDateSelected(newDate);
  70. }
  71. Future<void> _selectDate() async {
  72. final DateTime? picked = await showDatePicker(
  73. context: context,
  74. initialDate: _selectedDate ?? DateTime.now(),
  75. firstDate: widget.firstDate ?? DateTime(1900),
  76. lastDate: widget.lastDate ?? DateTime(2100),
  77. builder: (context, child) {
  78. return Theme(
  79. data: Theme.of(context).copyWith(
  80. colorScheme: ColorScheme.light(
  81. primary: Theme.of(context).primaryColor,
  82. ),
  83. ),
  84. child: child!,
  85. );
  86. },
  87. );
  88. if (picked != null && picked != _selectedDate) {
  89. _updateDate(picked);
  90. }
  91. }
  92. void _clearDate() {
  93. _updateDate(null);
  94. }
  95. String? _validateDate(String? value) {
  96. if (widget.isRequired && _selectedDate == null) {
  97. return '请选择日期';
  98. }
  99. return null;
  100. }
  101. @override
  102. Widget build(BuildContext context) {
  103. return TextFormField(
  104. controller: _controller,
  105. readOnly: true,
  106. style: widget.textStyle ?? Theme.of(context).textTheme.bodyMedium,
  107. decoration: (widget.decoration ?? InputDecoration(
  108. labelText: widget.labelText,
  109. hintText: widget.hintText,
  110. suffixIcon: _buildSuffixIcon(),
  111. )).copyWith(
  112. errorStyle: const TextStyle(height: 0.7),
  113. ),
  114. onTap: _selectDate,
  115. validator: widget.autoValidate ? _validateDate : null,
  116. );
  117. }
  118. Widget _buildSuffixIcon() {
  119. return Row(
  120. mainAxisSize: MainAxisSize.min,
  121. children: [
  122. if (widget.allowClear && _selectedDate != null)
  123. IconButton(
  124. icon: const Icon(Icons.clear, size: 18),
  125. onPressed: _clearDate,
  126. ),
  127. const Padding(
  128. padding: EdgeInsets.only(right: 8),
  129. child: Icon(Icons.calendar_today, size: 18),
  130. ),
  131. ],
  132. );
  133. }
  134. @override
  135. void dispose() {
  136. _controller.dispose();
  137. super.dispose();
  138. }
  139. }