import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:star_lock/tools/pickers/style/picker_style.dart'; import 'package:star_lock/tools/pickers/time_picker/model/date_item_model.dart'; import 'package:star_lock/tools/pickers/time_picker/model/date_mode.dart'; import 'package:star_lock/tools/pickers/time_picker/model/date_time_data.dart'; import 'package:star_lock/tools/pickers/time_picker/model/date_type.dart'; import 'package:star_lock/tools/pickers/time_picker/model/pduration.dart'; import 'package:star_lock/tools/pickers/time_picker/model/suffix.dart'; // import 'package:flutter_pickers/style/picker_style.dart'; // import 'package:flutter_pickers/time_picker/model/date_item_model.dart'; // import 'package:flutter_pickers/time_picker/model/date_mode.dart'; // import 'package:flutter_pickers/time_picker/model/date_time_data.dart'; // import 'package:flutter_pickers/time_picker/model/date_type.dart'; // import 'package:flutter_pickers/time_picker/model/pduration.dart'; // import 'package:flutter_pickers/time_picker/model/suffix.dart'; import '../time_utils.dart'; typedef DateCallback = Function(PDuration res); class DatePickerRoute extends PopupRoute { DatePickerRoute({ required this.initDate, required this.maxDate, required this.minDate, this.mode, this.pickerStyle, this.suffix, this.onChanged, this.onConfirm, this.onCancel, this.theme, this.barrierLabel, this.hourShow24, RouteSettings? settings, }) : super(settings: settings); final DateMode? mode; late final PDuration initDate; late final PDuration maxDate; late final PDuration minDate; final Suffix? suffix; final ThemeData? theme; final DateCallback? onChanged; final DateCallback? onConfirm; final Function(bool isCancel)? onCancel; final PickerStyle? pickerStyle; bool? hourShow24; @override Duration get transitionDuration => const Duration(milliseconds: 200); @override bool get barrierDismissible => true; @override bool didPop(T? result) { if (onCancel != null) { if (result == null) { onCancel!(false); } else if (!(result as bool)) { onCancel!(true); } } return super.didPop(result); } @override final String? barrierLabel; @override Color get barrierColor => Colors.black54; AnimationController? _animationController; @override AnimationController createAnimationController() { assert(_animationController == null); _animationController = BottomSheet.createAnimationController(navigator!.overlay!); return _animationController!; } @override Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { Widget bottomSheet = MediaQuery.removePadding( context: context, removeTop: true, child: _PickerContentView( mode: mode, initData: initDate, maxDate: maxDate, minDate: minDate, pickerStyle: pickerStyle!, route: this, hourShow24: hourShow24 ?? false, ), ); if (theme != null) { bottomSheet = Theme(data: theme!, child: bottomSheet); } return bottomSheet; } } class _PickerContentView extends StatefulWidget { _PickerContentView({ required this.initData, required this.pickerStyle, required this.maxDate, required this.minDate, required this.route, Key? key, this.mode, this.hourShow24 = false, }) : super(key: key); final DateMode? mode; late final PDuration initData; late final DatePickerRoute route; late final PickerStyle pickerStyle; // 限制时间 late final PDuration maxDate; late final PDuration minDate; // 是否显示24小时 late final bool hourShow24; @override State createState() => _PickerState( mode, initData, maxDate, minDate, pickerStyle, hourShow24); } class _PickerState extends State<_PickerContentView> { _PickerState(DateMode? mode, this._initSelectData, this.maxDate, this.minDate, this._pickerStyle, this.hourShow24) { _dateItemModel = DateItemModel.parse(mode!); pickerItemHeight = _pickerStyle.pickerItemHeight; _init(); } late final PickerStyle _pickerStyle; // 是否显示 [年月日时分秒] late DateItemModel _dateItemModel; // 初始 设置选中的数据 late final PDuration _initSelectData; // 选中的数据 用于回传 late PDuration _selectData; // 所有item 对应的数据 late DateTimeData _dateTimeData; // 限制时间 late final PDuration maxDate; late final PDuration minDate; // 是否显示24小时 bool hourShow24 = false; Animation? animation; Map scrollCtrl = {}; // 选择器 高度 单独提出来,用来解决修改数据 不及时更新的BUG late double pickerItemHeight; _init() { scrollCtrl.clear(); _dateTimeData = DateTimeData(); int index = 0; // 初始选中值 Index _selectData = PDuration(); /// minDate 和 maxDate 都初始化了,可以省略很多空判断,直接比较选中值 是否相等 _selectData.month == minDate.month /// -------年 if (_dateItemModel.year) { index = 0; _dateTimeData.year = TimeUtils.calcYears(begin: minDate.year!, end: maxDate.year!); if (_initSelectData.year != null) { index = _dateTimeData.year.indexOf(_initSelectData.year); index = index < 0 ? 0 : index; } _selectData.year = _dateTimeData.year[index]; scrollCtrl[DateType.Year] = FixedExtentScrollController(initialItem: index); } /// ------月 // 选中的月 用于之后 day 的计算 int selectMonth = 1; if (_dateItemModel.month) { index = 0; int begin = 1; int end = 12; // 限制区域 if (intNotEmpty(minDate.month) && _selectData.year == minDate.year) { begin = minDate.month!; } if (intNotEmpty(maxDate.month) && _selectData.year == maxDate.year) { end = maxDate.month!; } _dateTimeData.month = TimeUtils.calcMonth(begin: begin, end: end); if (_initSelectData.month != null) { index = _dateTimeData.month.indexOf(_initSelectData.month); index = index < 0 ? 0 : index; } selectMonth = _dateTimeData.month[index]; _selectData.month = selectMonth; scrollCtrl[DateType.Month] = FixedExtentScrollController(initialItem: index); } /// -------日 (有日肯定有 年月数据) if (_dateItemModel.day) { index = 0; int begin = 1; int end = 31; // 限制区域 if (intNotEmpty(minDate.day) || intNotEmpty(maxDate.day)) { if (_selectData.year == minDate.year && _selectData.month == minDate.month) { begin = minDate.day!; } if (_selectData.year == maxDate.year && _selectData.month == maxDate.month) { end = maxDate.day!; } } _dateTimeData.day = TimeUtils.calcDay(_initSelectData.year!, selectMonth, begin: begin, end: end); if (_initSelectData.day != null) { index = _dateTimeData.day.indexOf(_initSelectData.day); index = index < 0 ? 0 : index; } _selectData.day = _dateTimeData.day[index]; scrollCtrl[DateType.Day] = FixedExtentScrollController(initialItem: index); } /// ---------时 if (_dateItemModel.hour) { index = 0; int begin = 0; int end = hourShow24 ? 24 : 23; // 限制区域 if (intNotEmpty(minDate.hour)) { begin = minDate.hour!; } if (intNotEmpty(maxDate.hour)) { end = maxDate.hour!; } _dateTimeData.hour = TimeUtils.calcHour(begin: begin, end: end, hourShow24: hourShow24); if (_initSelectData.hour != null) { index = _dateTimeData.hour.indexOf(_initSelectData.hour); index = index < 0 ? 0 : index; } _selectData.hour = _dateTimeData.hour[index]; scrollCtrl[DateType.Hour] = FixedExtentScrollController(initialItem: index); } /// ---------分 if (_dateItemModel.minute) { index = 0; int begin = 0; int end = 59; // 限制区域 if (intNotEmpty(minDate.minute) || intNotEmpty(maxDate.minute)) { if (_dateItemModel.hour) { // 如果有上级 还有时,要根据时再判断 if (_selectData.hour == minDate.hour) { begin = minDate.minute!; } if (_selectData.hour == maxDate.hour) { end = maxDate.minute!; } } else { // 上级没有时间限制 直接取 if (intNotEmpty(minDate.minute)) { begin = minDate.minute!; } if (intNotEmpty(maxDate.minute)) { end = maxDate.minute!; } } } _dateTimeData.minute = TimeUtils.calcMinAndSecond(begin: begin, end: end); if (_initSelectData.minute != null) { index = _dateTimeData.minute.indexOf(_initSelectData.minute); index = index < 0 ? 0 : index; } _selectData.minute = _dateTimeData.minute[index]; scrollCtrl[DateType.Minute] = FixedExtentScrollController(initialItem: index); } /// --------秒 if (_dateItemModel.second) { index = 0; int begin = 0; int end = 59; // 限制区域 if (intNotEmpty(minDate.second) || intNotEmpty(maxDate.second)) { if (_dateItemModel.hour && _dateItemModel.minute) { // 如果有上级 还有时 分,要根据时分再判断 if (_selectData.hour == minDate.hour && _selectData.minute == minDate.minute) { begin = minDate.second!; } if (_selectData.hour == maxDate.hour && _selectData.minute == maxDate.minute) { end = maxDate.second!; } } else if (_dateItemModel.minute) { /// 上级没有时,只有分限制 if (_selectData.minute == minDate.minute) { begin = minDate.second!; } if (_selectData.minute == maxDate.minute) { end = maxDate.second!; } } else { /// 上级没有时间限制 直接取 if (intNotEmpty(minDate.second)) { begin = minDate.second!; } if (intNotEmpty(maxDate.second)) { end = maxDate.second!; } } } _dateTimeData.second = TimeUtils.calcMinAndSecond(begin: begin, end: end); if (_initSelectData.second != null) { index = _dateTimeData.second.indexOf(_initSelectData.second); index = index < 0 ? 0 : index; } _selectData.second = _dateTimeData.second[index]; scrollCtrl[DateType.Second] = FixedExtentScrollController(initialItem: index); } } @override void dispose() { scrollCtrl.forEach((key, value) { value.dispose(); }); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( child: AnimatedBuilder( animation: widget.route.animation!, builder: (BuildContext context, Widget? child) { return ClipRect( child: CustomSingleChildLayout( delegate: _BottomPickerLayout( widget.route.animation!.value, _pickerStyle), child: GestureDetector( child: Material( color: Colors.transparent, child: _renderPickerView(), ), ), ), ); }, ), ); } void _setPicker(DateType dateType, int selectIndex) { // 得到新的选中的数据 var selectValue = _dateTimeData.getListByName(dateType)[selectIndex]; // 更新选中数据 _selectData.setSingle(dateType, selectValue); switch (dateType) { case DateType.Year: _setYear(); break; case DateType.Month: _setMonth(); break; case DateType.Day: break; case DateType.Hour: _setHour(); break; case DateType.Minute: _setMinute(); break; case DateType.Second: break; } _notifyLocationChanged(); } // -------------------- set begin ------------ void _setYear() { // 可能造成 月 日 list的改变 if (_dateItemModel.month) { // 月的数据是否需要更新 bool updateMonth = false; bool updateDay = false; /// 如果只有月 int beginMonth = 1; int endMonth = 12; // 限制区域 if (intNotEmpty(minDate.month) && _selectData.year == minDate.year) { beginMonth = minDate.month!; } if (intNotEmpty(maxDate.month) && _selectData.year == maxDate.year) { endMonth = maxDate.month!; } var resultMonth = TimeUtils.calcMonth(begin: beginMonth, end: endMonth); int jumpToIndexMonth = 0; if (!listEquals(_dateTimeData.month, resultMonth)) { //可能 选中的月份 由于设置了新数据后没有了 // 小于不用考虑 会进else if (_selectData.month! > resultMonth.last) { jumpToIndexMonth = resultMonth.length - 1; } else { jumpToIndexMonth = resultMonth.indexOf(_selectData.month); } jumpToIndexMonth = jumpToIndexMonth < 0 ? 0 : jumpToIndexMonth; _selectData.month = resultMonth[jumpToIndexMonth]; updateMonth = true; } /// 还有 日 int jumpToIndexDay = 0; // 新的day 数据 var resultDay; if (_dateItemModel.day) { int beginDay = 1; int endDay = 31; // 限制区域 if (intNotEmpty(minDate.day) || intNotEmpty(maxDate.day)) { if (_selectData.year == minDate.year && _selectData.month == minDate.month) { beginDay = minDate.day!; } if (_selectData.year == maxDate.year && _selectData.month == maxDate.month) { endDay = maxDate.day!; } } resultDay = TimeUtils.calcDay(_selectData.year!, _selectData.month!, begin: beginDay, end: endDay); if (!listEquals(_dateTimeData.day, resultDay)) { //可能 选中的年 月份 由于设置了新数据后没有了 // 小于不用考虑 会进else if (_selectData.day! > resultDay.last) { jumpToIndexDay = resultDay.length - 1; } else { jumpToIndexDay = resultDay.indexOf(_selectData.day); } jumpToIndexDay = jumpToIndexDay < 0 ? 0 : jumpToIndexDay; _selectData.day = resultDay[jumpToIndexDay]; updateDay = true; } } if (updateMonth || updateDay) { setState(() { if (updateMonth) { _dateTimeData.month = resultMonth; scrollCtrl[DateType.Month]?.jumpToItem(jumpToIndexMonth); } if (updateDay) { _dateTimeData.day = resultDay; scrollCtrl[DateType.Day]?.jumpToItem(jumpToIndexDay); } /// FIX:https://github.com/flutter/flutter/issues/22999 pickerItemHeight = _pickerStyle.pickerItemHeight - Random().nextDouble() / 100000000; }); } } } void _setMonth() { // 可能造成 日 list的改变 bool updateDay = false; int jumpToIndexDay = 0; // 新的day 数据 var resultDay; if (_dateItemModel.day) { int beginDay = 1; int endDay = 31; // 限制区域 if (intNotEmpty(minDate.day) || intNotEmpty(maxDate.day)) { if (_selectData.year == minDate.year && _selectData.month == minDate.month) { beginDay = minDate.day!; } if (_selectData.year == maxDate.year && _selectData.month == maxDate.month) { endDay = maxDate.day!; } } resultDay = TimeUtils.calcDay(_selectData.year!, _selectData.month!, begin: beginDay, end: endDay); if (!listEquals(_dateTimeData.day, resultDay)) { //可能 选中的年 月份 由于设置了新数据后没有了 // 小于不用考虑 会进else if (_selectData.day! > resultDay.last) { jumpToIndexDay = resultDay.length - 1; } else { jumpToIndexDay = resultDay.indexOf(_selectData.day); } jumpToIndexDay = jumpToIndexDay < 0 ? 0 : jumpToIndexDay; _selectData.day = resultDay[jumpToIndexDay]; updateDay = true; } } if (updateDay) { setState(() { _dateTimeData.day = resultDay; scrollCtrl[DateType.Day]?.jumpToItem(jumpToIndexDay); /// FIX:https://github.com/flutter/flutter/issues/22999 pickerItemHeight = _pickerStyle.pickerItemHeight - Random().nextDouble() / 100000000; }); } } void _setHour() { // 可能造成 分 秒 list的改变 if (_dateItemModel.minute) { // 月的数据是否需要更新 bool updateMinute = false; bool updateSecond = false; /// 如果只有分 int beginMinute = 0; int endMinute = 59; // 限制区域 if (intNotEmpty(minDate.minute) && _selectData.hour == minDate.hour) { beginMinute = minDate.minute!; } if (intNotEmpty(maxDate.minute) && _selectData.hour == maxDate.hour) { endMinute = maxDate.minute!; } var resultMinute = TimeUtils.calcMinAndSecond(begin: beginMinute, end: endMinute); int jumpToIndexMinute = 0; if (!listEquals(_dateTimeData.month, resultMinute)) { //可能 选中的时间 由于设置了新数据后没有了 // 小于不用考虑 会进else if (_selectData.minute! > resultMinute.last) { jumpToIndexMinute = resultMinute.length - 1; } else { jumpToIndexMinute = resultMinute.indexOf(_selectData.minute); } jumpToIndexMinute = jumpToIndexMinute < 0 ? 0 : jumpToIndexMinute; _selectData.minute = resultMinute[jumpToIndexMinute]; updateMinute = true; } /// 还有 秒 int jumpToIndexSecond = 0; // 新的day 数据 var resultSecond; if (_dateItemModel.second) { int beginSecond = 0; int endSecond = 59; // 限制区域 if (intNotEmpty(minDate.second) || intNotEmpty(maxDate.second)) { if (_selectData.hour == minDate.hour && _selectData.minute == minDate.minute) { beginSecond = minDate.second!; } if (_selectData.hour == maxDate.hour && _selectData.minute == maxDate.minute) { endSecond = maxDate.second!; } } resultSecond = TimeUtils.calcMinAndSecond(begin: beginSecond, end: endSecond); if (!listEquals(_dateTimeData.second, resultSecond)) { //可能 选中的时 分 由于设置了新数据后没有了 // 小于不用考虑 会进else if (_selectData.second! > resultSecond.last) { jumpToIndexSecond = resultSecond.length - 1; } else { jumpToIndexSecond = resultSecond.indexOf(_selectData.second); } jumpToIndexSecond = jumpToIndexSecond < 0 ? 0 : jumpToIndexSecond; _selectData.second = resultSecond[jumpToIndexSecond]; updateSecond = true; } } if (updateMinute || updateSecond) { setState(() { if (updateMinute) { _dateTimeData.minute = resultMinute; scrollCtrl[DateType.Minute]?.jumpToItem(jumpToIndexMinute); } if (updateSecond) { _dateTimeData.second = resultSecond; scrollCtrl[DateType.Second]?.jumpToItem(jumpToIndexSecond); } /// FIX:https://github.com/flutter/flutter/issues/22999 pickerItemHeight = _pickerStyle.pickerItemHeight - Random().nextDouble() / 100000000; }); } } } void _setMinute() { // 可能造成 秒 list的改变 bool updateSecond = false; int jumpToIndexSecond = 0; // 新的day 数据 var resultSecond; if (_dateItemModel.second) { int beginSecond = 0; int endSecond = 59; // 限制区域 if (intNotEmpty(minDate.second) || intNotEmpty(maxDate.second)) { if (_dateItemModel.hour) { // 如果上面还有 时 if (_selectData.hour == minDate.hour && _selectData.minute == minDate.minute) { beginSecond = minDate.second!; } if (_selectData.hour == maxDate.hour && _selectData.minute == maxDate.minute) { endSecond = maxDate.second!; } } else { // 没有时,分秒 if (_selectData.minute == minDate.minute) { beginSecond = minDate.second!; } if (_selectData.minute == maxDate.minute) { endSecond = maxDate.second!; } } } resultSecond = TimeUtils.calcMinAndSecond(begin: beginSecond, end: endSecond); if (!listEquals(_dateTimeData.second, resultSecond)) { //可能 选中的分 由于设置了新数据后没有了 // 小于不用考虑 会进else if (_selectData.second! > resultSecond.last) { jumpToIndexSecond = resultSecond.length - 1; } else { jumpToIndexSecond = resultSecond.indexOf(_selectData.second); } jumpToIndexSecond = jumpToIndexSecond < 0 ? 0 : jumpToIndexSecond; _selectData.second = resultSecond[jumpToIndexSecond]; updateSecond = true; } } if (updateSecond) { setState(() { _dateTimeData.second = resultSecond; scrollCtrl[DateType.Second]?.jumpToItem(jumpToIndexSecond); /// FIX:https://github.com/flutter/flutter/issues/22999 pickerItemHeight = _pickerStyle.pickerItemHeight - Random().nextDouble() / 100000000; }); } } // -------------------- set end ------------ void _notifyLocationChanged() { if (widget.route.onChanged != null) { widget.route.onChanged!(_selectData); } } double _pickerFontSize(String text) { if (text == '') return 18.0; if (_dateItemModel.length == 6 && (text.length > 4 && text.length <= 6)) { return 16.0; } if (text.length <= 6) { return 18.0; } else if (text.length < 9) { return 16.0; } else if (text.length < 13) { return 12.0; } else { return 10.0; } } Widget _renderPickerView() { Widget itemView = _renderItemView(); if (!_pickerStyle.showTitleBar && _pickerStyle.menu == null) { return itemView; } List viewList = []; if (_pickerStyle.showTitleBar) { viewList.add(_titleView()); } if (_pickerStyle.menu != null) { viewList.add(_pickerStyle.menu!); } viewList.add(itemView); return Column(children: viewList); } Widget _renderItemView() { // 选择器 List pickerList = []; if (_dateItemModel.year) pickerList.add(pickerView(DateType.Year)); if (_dateItemModel.month) pickerList.add(pickerView(DateType.Month)); if (_dateItemModel.day) pickerList.add(pickerView(DateType.Day)); if (_dateItemModel.hour) pickerList.add(pickerView(DateType.Hour)); if (_dateItemModel.minute) pickerList.add(pickerView(DateType.Minute)); if (_dateItemModel.second) pickerList.add(pickerView(DateType.Second)); return Container( height: _pickerStyle.pickerHeight, color: _pickerStyle.backgroundColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: pickerList, ), ); } /// CupertinoPicker.builder Widget pickerView(DateType dateType) { return Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 2), child: CupertinoPicker.builder( /// key :年月拼接 就不会重复了 /// 最好别使用key 会生成新的widget /// 官方的bug : https://github.com/flutter/flutter/issues/22999 /// 临时方法 通过修改height scrollController: scrollCtrl[dateType], itemExtent: pickerItemHeight, selectionOverlay: _pickerStyle.itemOverlay, onSelectedItemChanged: (int selectIndex) => _setPicker(dateType, selectIndex), childCount: _dateTimeData.getListByName(dateType).length, itemBuilder: (_, index) { String text = '${_dateTimeData.getListByName(dateType)[index]}${widget.route.suffix?.getSingle(dateType)}'; return Align( alignment: Alignment.center, child: Text(text, style: TextStyle( color: _pickerStyle.textColor, fontSize: _pickerStyle.textSize ?? _pickerFontSize(text), ), textAlign: TextAlign.start)); }, ), ), ); } // 选择器上面的view Widget _titleView() { return Container( height: _pickerStyle.pickerTitleHeight, decoration: _pickerStyle.headDecoration, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ /// 取消按钮 InkWell( onTap: () => Navigator.pop(context, false), child: _pickerStyle.cancelButton), /// 标题 Expanded(child: _pickerStyle.title), /// 确认按钮 InkWell( onTap: () { if (widget.route.onConfirm != null) { widget.route.onConfirm!(_selectData); } Navigator.pop(context, true); }, child: _pickerStyle.commitButton) ], ), ); } } class _BottomPickerLayout extends SingleChildLayoutDelegate { _BottomPickerLayout(this.progress, this.pickerStyle); final double progress; final PickerStyle pickerStyle; @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { double maxHeight = pickerStyle.pickerHeight; if (pickerStyle.showTitleBar) { maxHeight += pickerStyle.pickerTitleHeight; } if (pickerStyle.menu != null) { maxHeight += pickerStyle.menuHeight; } return BoxConstraints( minWidth: constraints.maxWidth, maxWidth: constraints.maxWidth, minHeight: 0.0, maxHeight: maxHeight); } @override Offset getPositionForChild(Size size, Size childSize) { double height = size.height - childSize.height * progress; return Offset(0.0, height); } @override bool shouldRelayout(_BottomPickerLayout oldDelegate) { return progress != oldDelegate.progress; } }