在flutter中用flutter_datetime_picker只选择年月,或只选择年,以及选择日期
flutter_datetime_picker组件没有配置只选择年或者月的,只能选择日期或者时间,现重新修改组件支持此功能
效果如下:只选择年月
只选择年
日期就不用展示了,组件自己就有
导入包flutter_datetime_picker: 1.5.1
修改组件的文件
修改后的flutter_datetime_picker.dart代码
library flutter_datetime_picker; import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_datetime_picker/src/datetime_picker_theme.dart' as example; import 'package:flutter_datetime_picker/src/date_model.dart'; import 'package:flutter_datetime_picker/src/i18n_model.dart'; export 'package:flutter_datetime_picker/src/datetime_picker_theme.dart'; export 'package:flutter_datetime_picker/src/date_model.dart'; export 'package:flutter_datetime_picker/src/i18n_model.dart'; typedef DateChangedCallback(DateTime time); typedef DateCancelledCallback(); typedef String? StringAtIndexCallBack(int index); class DatePicker { /// /// Display date picker bottom sheet. /// static Future<DateTime?> showDatePicker(BuildContext context, { bool showTitleActions: true, bool isShowDay: true, bool isShowMonth: true, DateTime? minTime, DateTime? maxTime, DateChangedCallback? onChanged, DateChangedCallback? onConfirm, DateCancelledCallback? onCancel, locale: LocaleType.en, DateTime? currentTime, example.DatePickerTheme? theme, }) async { return await Navigator.push( context, _DatePickerRoute( showTitleActions: showTitleActions, onChanged: onChanged, onConfirm: onConfirm, onCancel: onCancel, locale: locale, theme: theme, isShowDay: isShowDay, isShowMonth: isShowMonth, barrierLabel: MaterialLocalizations .of(context) .modalBarrierDismissLabel, pickerModel: DatePickerModel( currentTime: currentTime, maxTime: maxTime, minTime: minTime, locale: locale, ), ), ); } /// /// Display time picker bottom sheet. /// static Future<DateTime?> showTimePicker(BuildContext context, { bool showTitleActions: true, bool isShowDay: true, bool isShowMonth: true, bool showSecondsColumn: true, DateChangedCallback? onChanged, DateChangedCallback? onConfirm, DateCancelledCallback? onCancel, locale: LocaleType.en, DateTime? currentTime, example.DatePickerTheme? theme, }) async { return await Navigator.push( context, _DatePickerRoute( showTitleActions: showTitleActions, onChanged: onChanged, onConfirm: onConfirm, onCancel: onCancel, locale: locale, theme: theme, isShowDay: isShowDay, isShowMonth: isShowMonth, barrierLabel: MaterialLocalizations .of(context) .modalBarrierDismissLabel, pickerModel: TimePickerModel( currentTime: currentTime, locale: locale, showSecondsColumn: showSecondsColumn, ), ), ); } /// /// Display time picker bottom sheet with AM/PM. /// static Future<DateTime?> showTime12hPicker(BuildContext context, { bool showTitleActions: true, bool isShowDay: true, bool isShowMonth: true, DateChangedCallback? onChanged, DateChangedCallback? onConfirm, DateCancelledCallback? onCancel, locale: LocaleType.en, DateTime? currentTime, example.DatePickerTheme? theme, }) async { return await Navigator.push( context, _DatePickerRoute( showTitleActions: showTitleActions, onChanged: onChanged, onConfirm: onConfirm, onCancel: onCancel, locale: locale, theme: theme, isShowDay: isShowDay, isShowMonth: isShowMonth, barrierLabel: MaterialLocalizations .of(context) .modalBarrierDismissLabel, pickerModel: Time12hPickerModel( currentTime: currentTime, locale: locale, ), ), ); } /// /// Display date&time picker bottom sheet. /// static Future<DateTime?> showDateTimePicker(BuildContext context, { bool showTitleActions: true, bool isShowDay: true, bool isShowMonth: true, DateTime? minTime, DateTime? maxTime, DateChangedCallback? onChanged, DateChangedCallback? onConfirm, DateCancelledCallback? onCancel, locale: LocaleType.en, DateTime? currentTime, example.DatePickerTheme? theme, }) async { return await Navigator.push( context, _DatePickerRoute( showTitleActions: showTitleActions, onChanged: onChanged, onConfirm: onConfirm, onCancel: onCancel, locale: locale, theme: theme, isShowDay: isShowDay, isShowMonth: isShowMonth, barrierLabel: MaterialLocalizations .of(context) .modalBarrierDismissLabel, pickerModel: DateTimePickerModel( currentTime: currentTime, minTime: minTime, maxTime: maxTime, locale: locale, ), ), ); } /// /// Display date picker bottom sheet witch custom picker model. /// static Future<DateTime?> showPicker(BuildContext context, { bool showTitleActions: true, bool isShowDay: true, bool isShowMonth: true, DateChangedCallback? onChanged, DateChangedCallback? onConfirm, DateCancelledCallback? onCancel, locale: LocaleType.en, BasePickerModel? pickerModel, example.DatePickerTheme? theme, }) async { return await Navigator.push( context, _DatePickerRoute( showTitleActions: showTitleActions, onChanged: onChanged, onConfirm: onConfirm, onCancel: onCancel, locale: locale, theme: theme, isShowDay: isShowDay, isShowMonth: isShowMonth, barrierLabel: MaterialLocalizations .of(context) .modalBarrierDismissLabel, pickerModel: pickerModel, ), ); } } class _DatePickerRoute<T> extends PopupRoute<T> { _DatePickerRoute({ this.showTitleActions, this.onChanged, this.onConfirm, this.onCancel, example.DatePickerTheme? theme, this.barrierLabel, this.locale, this.isShowDay, this.isShowMonth, RouteSettings? settings, BasePickerModel? pickerModel, }) : this.pickerModel = pickerModel ?? DatePickerModel(), this.theme = theme ?? example.DatePickerTheme(), super(settings: settings); final bool? showTitleActions; final DateChangedCallback? onChanged; final DateChangedCallback? onConfirm; final DateCancelledCallback? onCancel; final LocaleType? locale; final bool? isShowDay; final bool? isShowMonth; final example.DatePickerTheme theme; final BasePickerModel pickerModel; @override Duration get transitionDuration => const Duration(milliseconds: 200); @override bool get barrierDismissible => true; @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<double> animation, Animation<double> secondaryAnimation) { Widget bottomSheet = MediaQuery.removePadding( context: context, removeTop: true, child: _DatePickerComponent( onChanged: onChanged, locale: this.locale, route: this, pickerModel: pickerModel, isShowDay: isShowDay, isShowMonth: isShowMonth, ), ); return InheritedTheme.captureAll(context, bottomSheet); } } class _DatePickerComponent extends StatefulWidget { _DatePickerComponent({ Key? key, required this.route, required this.pickerModel, this.isShowDay, this.isShowMonth, this.onChanged, this.locale, }) : super(key: key); final DateChangedCallback? onChanged; final _DatePickerRoute route; final LocaleType? locale; final BasePickerModel pickerModel; bool? isShowDay; bool? isShowMonth; @override State<StatefulWidget> createState() { return _DatePickerState(); } } class _DatePickerState extends State<_DatePickerComponent> { late FixedExtentScrollController leftScrollCtrl, middleScrollCtrl, rightScrollCtrl; @override void initState() { super.initState(); refreshScrollOffset(); } void refreshScrollOffset() { // print('refreshScrollOffset ${widget.pickerModel.currentRightIndex()}'); leftScrollCtrl = FixedExtentScrollController( initialItem: widget.pickerModel.currentLeftIndex()); middleScrollCtrl = FixedExtentScrollController( initialItem: widget.pickerModel.currentMiddleIndex()); rightScrollCtrl = FixedExtentScrollController( initialItem: widget.pickerModel.currentRightIndex()); } @override Widget build(BuildContext context) { example.DatePickerTheme theme = widget.route.theme; return GestureDetector( child: AnimatedBuilder( animation: widget.route.animation!, builder: (BuildContext context, Widget? child) { final double bottomPadding = MediaQuery .of(context) .padding .bottom; return ClipRect( child: CustomSingleChildLayout( delegate: _BottomPickerLayout( widget.route.animation!.value, theme, showTitleActions: widget.route.showTitleActions!, bottomPadding: bottomPadding, ), child: GestureDetector( child: Material( color: theme.backgroundColor ?? Colors.white, child: _renderPickerView(theme), ), ), ), ); }, ), ); } void _notifyDateChanged() { if (widget.onChanged != null) { widget.onChanged!(widget.pickerModel.finalTime()!); } } Widget _renderPickerView(example.DatePickerTheme theme) { Widget itemView = _renderItemView(theme); if (widget.route.showTitleActions == true) { return Column( children: <Widget>[ _renderTitleActionsView(theme), itemView, ], ); } return itemView; } Widget _renderColumnView(ValueKey key, example.DatePickerTheme theme, StringAtIndexCallBack stringAtIndexCB, ScrollController scrollController, int layoutProportion, ValueChanged<int> selectedChangedWhenScrolling, ValueChanged<int> selectedChangedWhenScrollEnd,) { return Expanded( flex: layoutProportion, child: Container( padding: EdgeInsets.all(8.0), height: theme.containerHeight, decoration: BoxDecoration(color: theme.backgroundColor), child: NotificationListener( onNotification: (ScrollNotification notification) { if (notification.depth == 0 && notification is ScrollEndNotification && notification.metrics is FixedExtentMetrics) { final FixedExtentMetrics metrics = notification.metrics as FixedExtentMetrics; final int currentItemIndex = metrics.itemIndex; selectedChangedWhenScrollEnd(currentItemIndex); } return false; }, child: CupertinoPicker.builder( key: key, backgroundColor: theme.backgroundColor, scrollController: scrollController as FixedExtentScrollController, itemExtent: theme.itemHeight, onSelectedItemChanged: (int index) { selectedChangedWhenScrolling(index); }, useMagnifier: true, itemBuilder: (BuildContext context, int index) { final content = stringAtIndexCB(index); if (content == null) { return null; } return Container( height: theme.itemHeight, alignment: Alignment.center, child: Text( content, style: theme.itemStyle, textAlign: TextAlign.start, ), ); }, ), ), ), ); } Widget _renderItemView(example.DatePickerTheme theme) { return Container( color: theme.backgroundColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Container( child: widget.pickerModel.layoutProportions()[0] > 0 ? _renderColumnView( ValueKey(widget.pickerModel.currentLeftIndex()), theme, widget.pickerModel.leftStringAtIndex, leftScrollCtrl, widget.pickerModel.layoutProportions()[0], (index) { widget.pickerModel.setLeftIndex(index); }, (index) { setState(() { refreshScrollOffset(); _notifyDateChanged(); }); }) : null, ), Text( widget.pickerModel.leftDivider(), style: theme.itemStyle, ), widget.isShowMonth ?? true ? Container( child: widget.pickerModel.layoutProportions()[1] > 0 ? _renderColumnView( ValueKey(widget.pickerModel.currentLeftIndex()), theme, widget.pickerModel.middleStringAtIndex, middleScrollCtrl, widget.pickerModel.layoutProportions()[1], (index) { widget.pickerModel.setMiddleIndex(index); }, (index) { setState(() { refreshScrollOffset(); _notifyDateChanged(); }); }) : null, ) : Container(), Text( widget.pickerModel.rightDivider(), style: theme.itemStyle, ), widget.isShowDay ?? true ? Container( child: widget.pickerModel.layoutProportions()[2] > 0 ? _renderColumnView( ValueKey(widget.pickerModel.currentMiddleIndex() * 100 + widget.pickerModel.currentLeftIndex()), theme, widget.pickerModel.rightStringAtIndex, rightScrollCtrl, widget.pickerModel.layoutProportions()[2], (index) { widget.pickerModel.setRightIndex(index); }, (index) { setState(() { refreshScrollOffset(); _notifyDateChanged(); }); }) : null, ) : Container(), ], ), ); } // Title View Widget _renderTitleActionsView(example.DatePickerTheme theme) { final done = _localeDone(); final cancel = _localeCancel(); return Container( height: theme.titleHeight, decoration: BoxDecoration( color: theme.headerColor ?? theme.backgroundColor, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Container( height: theme.titleHeight, child: CupertinoButton( pressedOpacity: 0.3, padding: EdgeInsets.only(left: 16, top: 0), child: Text( '$cancel', style: theme.cancelStyle, ), onPressed: () { Navigator.pop(context); if (widget.route.onCancel != null) { widget.route.onCancel!(); } }, ), ), Container( height: theme.titleHeight, child: CupertinoButton( pressedOpacity: 0.3, padding: EdgeInsets.only(right: 16, top: 0), child: Text( '$done', style: theme.doneStyle, ), onPressed: () { Navigator.pop(context, widget.pickerModel.finalTime()); if (widget.route.onConfirm != null) { widget.route.onConfirm!(widget.pickerModel.finalTime()!); } }, ), ), ], ), ); } String _localeDone() { return i18nObjInLocale(widget.locale)['done'] as String; } String _localeCancel() { return i18nObjInLocale(widget.locale)['cancel'] as String; } } class _BottomPickerLayout extends SingleChildLayoutDelegate { _BottomPickerLayout(this.progress, this.theme, { this.itemCount, this.showTitleActions, this.bottomPadding = 0, }); final double progress; final int? itemCount; final bool? showTitleActions; final example.DatePickerTheme theme; final double bottomPadding; @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { double maxHeight = theme.containerHeight; if (showTitleActions == true) { maxHeight += theme.titleHeight; } return BoxConstraints( minWidth: constraints.maxWidth, maxWidth: constraints.maxWidth, minHeight: 0.0, maxHeight: maxHeight + bottomPadding, ); } @override Offset getPositionForChild(Size size, Size childSize) { final height = size.height - childSize.height * progress; return Offset(0.0, height); } @override bool shouldRelayout(_BottomPickerLayout oldDelegate) { return progress != oldDelegate.progress; } }
使用:
void showDatePicker() { DatePicker.showDatePicker( context, showTitleActions: true, isShowDay: false,//是否要选择日 isShowMonth: false,//是否要选择月 minTime: DateTime(1996, 3, 5), maxTime: DateTime(2099, 6, 7), onChanged: (date) { print('change $date'); }, onConfirm: (date) { print('confirm $date'); }, currentTime: DateTime.now(), locale: LocaleType.zh, ); } @override Widget build(BuildContext context) { return Column( children: <Widget>[ ElevatedButton( onPressed: showDatePicker, child: Text('Select Date'), ), ], ); }