221 lines
6.7 KiB
Dart
221 lines
6.7 KiB
Dart
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// 调用示例:
|
|
/// DateTime? picked = await BottomDateTimePicker.show(context);
|
|
/// if (picked != null) {
|
|
/// print('用户选择的时间:$picked');
|
|
/// }
|
|
class BottomDateTimePicker {
|
|
static Future<DateTime?> showDate(BuildContext context) {
|
|
return showModalBottomSheet<DateTime>(
|
|
context: context,
|
|
backgroundColor: Colors.white,
|
|
isScrollControlled: true,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
|
),
|
|
builder: (_) => _InlineDateTimePickerContent(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _InlineDateTimePickerContent extends StatefulWidget {
|
|
@override
|
|
State<_InlineDateTimePickerContent> createState() => _InlineDateTimePickerContentState();
|
|
}
|
|
|
|
class _InlineDateTimePickerContentState extends State<_InlineDateTimePickerContent> {
|
|
// 数据源
|
|
final List<int> years = List.generate(101, (i) => 1970 + i);
|
|
final List<int> months = List.generate(12, (i) => i + 1);
|
|
final List<int> days = List.generate(31, (i) => i + 1);
|
|
final List<int> hours = List.generate(24, (i) => i);
|
|
final List<int> minutes = List.generate(60, (i) => i);
|
|
|
|
// 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;
|
|
|
|
yearCtrl = FixedExtentScrollController(initialItem: years.indexOf(selectedYear));
|
|
monthCtrl = FixedExtentScrollController(initialItem: selectedMonth - 1);
|
|
dayCtrl = FixedExtentScrollController(initialItem: selectedDay - 1);
|
|
hourCtrl = FixedExtentScrollController(initialItem: selectedHour);
|
|
minuteCtrl = FixedExtentScrollController(initialItem: selectedMinute);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
yearCtrl.dispose();
|
|
monthCtrl.dispose();
|
|
dayCtrl.dispose();
|
|
hourCtrl.dispose();
|
|
minuteCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _checkAndClampToNow() {
|
|
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);
|
|
}
|
|
}
|
|
|
|
@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];
|
|
_checkAndClampToNow();
|
|
});
|
|
},
|
|
),
|
|
|
|
// 月
|
|
_buildPicker(
|
|
controller: monthCtrl,
|
|
items: months.map((e) => e.toString().padLeft(2, '0')).toList(),
|
|
onSelected: (idx) {
|
|
setState(() {
|
|
selectedMonth = months[idx];
|
|
_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<String> items,
|
|
required ValueChanged<int> onSelected,
|
|
}) {
|
|
return Expanded(
|
|
child: CupertinoPicker.builder(
|
|
scrollController: controller,
|
|
itemExtent: 32,
|
|
childCount: items.length,
|
|
onSelectedItemChanged: onSelected,
|
|
itemBuilder: (context, index) {
|
|
return Center(child: Text(items[index]));
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|