import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; /// 调用示例: /// DateTime? picked = await BottomDateTimePicker.showDate( /// context, /// allowFuture: true, // 添加此参数允许选择未来时间 /// ); /// if (picked != null) { /// print('用户选择的时间:$picked'); /// } class BottomDateTimePicker { static Future showDate(BuildContext context, {bool allowFuture = false}) { return showModalBottomSheet( context: context, backgroundColor: Colors.white, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(12)), ), builder: (_) => _InlineDateTimePickerContent(allowFuture: allowFuture), ); } } class _InlineDateTimePickerContent extends StatefulWidget { final bool allowFuture; // 允许未来 const _InlineDateTimePickerContent({Key? key, this.allowFuture = false}) : super(key: key); @override State<_InlineDateTimePickerContent> createState() => _InlineDateTimePickerContentState(); } class _InlineDateTimePickerContentState extends State<_InlineDateTimePickerContent> { // 数据源 final List years = List.generate(101, (i) => 1970 + i); final List months = List.generate(12, (i) => i + 1); final List hours = List.generate(24, (i) => i); final List minutes = List.generate(60, (i) => i); // 动态天数列表(根据年月变化) late List days; // Controllers late FixedExtentScrollController yearCtrl; late FixedExtentScrollController monthCtrl; late FixedExtentScrollController dayCtrl; late FixedExtentScrollController hourCtrl; late FixedExtentScrollController minuteCtrl; // 当前选中值 late int selectedYear; late int selectedMonth; late int selectedDay; late int selectedHour; late int selectedMinute; @override void initState() { super.initState(); final now = DateTime.now(); selectedYear = now.year; selectedMonth = now.month; selectedDay = now.day; selectedHour = now.hour; selectedMinute = now.minute; // 初始化天数列表 days = _getDaysInMonth(selectedYear, selectedMonth); yearCtrl = FixedExtentScrollController(initialItem: years.indexOf(selectedYear)); monthCtrl = FixedExtentScrollController(initialItem: selectedMonth - 1); dayCtrl = FixedExtentScrollController(initialItem: selectedDay - 1); hourCtrl = FixedExtentScrollController(initialItem: selectedHour); minuteCtrl = FixedExtentScrollController(initialItem: selectedMinute); } // 根据年月获取当月天数 List _getDaysInMonth(int year, int month) { final lastDay = DateUtils.getDaysInMonth(year, month); return List.generate(lastDay, (i) => i + 1); } // 更新天数列表并调整选中日期 void _updateDays() { final newDays = _getDaysInMonth(selectedYear, selectedMonth); final isDayValid = selectedDay <= newDays.length; setState(() { days = newDays; if (!isDayValid) { selectedDay = newDays.last; dayCtrl.jumpToItem(selectedDay - 1); } }); } // 检查并限制时间(当不允许未来时间时) void _checkAndClampToNow() { if (widget.allowFuture) return; // 允许未来时间时跳过检查 final picked = DateTime(selectedYear, selectedMonth, selectedDay, selectedHour, selectedMinute); final now = DateTime.now(); if (picked.isAfter(now)) { // 回滚到当前时间 selectedYear = now.year; selectedMonth = now.month; selectedDay = now.day; selectedHour = now.hour; selectedMinute = now.minute; // 更新各滚轮位置 yearCtrl.jumpToItem(years.indexOf(selectedYear)); monthCtrl.jumpToItem(selectedMonth - 1); dayCtrl.jumpToItem(selectedDay - 1); hourCtrl.jumpToItem(selectedHour); minuteCtrl.jumpToItem(selectedMinute); // 更新天数列表 _updateDays(); } } @override void dispose() { yearCtrl.dispose(); monthCtrl.dispose(); dayCtrl.dispose(); hourCtrl.dispose(); minuteCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SizedBox( height: 300, child: Column( children: [ // 顶部按钮 Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text("取消", style: TextStyle(color: Colors.grey)), ), TextButton( onPressed: () { final result = DateTime( selectedYear, selectedMonth, selectedDay, selectedHour, selectedMinute, ); Navigator.of(context).pop(result); }, child: const Text("确定", style: TextStyle(color: Colors.blue)), ), ], ), ), const Divider(height: 1), // 五列数字滚轮 Expanded( child: Row( children: [ // 年 _buildPicker( controller: yearCtrl, items: years.map((e) => e.toString()).toList(), onSelected: (idx) { setState(() { selectedYear = years[idx]; _updateDays(); // 年份变化时更新天数 _checkAndClampToNow(); }); }, ), // 月 _buildPicker( controller: monthCtrl, items: months.map((e) => e.toString().padLeft(2, '0')).toList(), onSelected: (idx) { setState(() { selectedMonth = months[idx]; _updateDays(); // 月份变化时更新天数 _checkAndClampToNow(); }); }, ), // 日 _buildPicker( controller: dayCtrl, items: days.map((e) => e.toString().padLeft(2, '0')).toList(), onSelected: (idx) { setState(() { selectedDay = days[idx]; _checkAndClampToNow(); }); }, ), // 时 _buildPicker( controller: hourCtrl, items: hours.map((e) => e.toString().padLeft(2, '0')).toList(), onSelected: (idx) { setState(() { selectedHour = hours[idx]; _checkAndClampToNow(); }); }, ), // 分 _buildPicker( controller: minuteCtrl, items: minutes.map((e) => e.toString().padLeft(2, '0')).toList(), onSelected: (idx) { setState(() { selectedMinute = minutes[idx]; _checkAndClampToNow(); }); }, ), ], ), ), ], ), ); } Widget _buildPicker({ required FixedExtentScrollController controller, required List items, required ValueChanged onSelected, }) { return Expanded( child: CupertinoPicker.builder( scrollController: controller, itemExtent: 32, childCount: items.length, onSelectedItemChanged: onSelected, itemBuilder: (context, index) { return Center(child: Text(items[index])); }, ), ); } }