flutter_datetime_picker.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. library flutter_datetime_picker;
  2. export 'package:flutter_datetime_picker/src/dateModel.dart';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter_datetime_picker/src/dateModel.dart';
  6. typedef DateChangedCallback(DateTime time);
  7. typedef String StringAtIndexCallBack(int index);
  8. const double _kDatePickerHeight = 210.0;
  9. const double _kDatePickerTitleHeight = 44.0;
  10. const double _kDatePickerItemHeight = 36.0;
  11. const double _kDatePickerFontSize = 18.0;
  12. class DatePicker {
  13. ///
  14. /// Display date picker bottom sheet.
  15. ///
  16. static void showDatePicker(BuildContext context,
  17. {bool showTitleActions: true,
  18. DateChangedCallback onChanged,
  19. DateChangedCallback onConfirm,
  20. locale: 'en',
  21. DateTime currentTime}) {
  22. Navigator.push(
  23. context,
  24. new _DatePickerRoute(
  25. showTitleActions: showTitleActions,
  26. onChanged: onChanged,
  27. onConfirm: onConfirm,
  28. locale: locale,
  29. theme: Theme.of(context, shadowThemeOnly: true),
  30. barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
  31. pickerModel: DatePickerModel(currentTime: currentTime, locale: locale)));
  32. }
  33. ///
  34. /// Display time picker bottom sheet.
  35. ///
  36. static void showTimePicker(BuildContext context,
  37. {bool showTitleActions: true,
  38. DateChangedCallback onChanged,
  39. DateChangedCallback onConfirm,
  40. locale: 'en',
  41. DateTime currentTime}) {
  42. Navigator.push(
  43. context,
  44. new _DatePickerRoute(
  45. showTitleActions: showTitleActions,
  46. onChanged: onChanged,
  47. onConfirm: onConfirm,
  48. locale: locale,
  49. theme: Theme.of(context, shadowThemeOnly: true),
  50. barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
  51. pickerModel: TimePickerModel(currentTime: currentTime, locale: locale)));
  52. }
  53. ///
  54. /// Display date&time picker bottom sheet.
  55. ///
  56. static void showDateTimePicker(BuildContext context,
  57. {bool showTitleActions: true,
  58. DateChangedCallback onChanged,
  59. DateChangedCallback onConfirm,
  60. locale: 'en',
  61. DateTime currentTime}) {
  62. Navigator.push(
  63. context,
  64. new _DatePickerRoute(
  65. showTitleActions: showTitleActions,
  66. onChanged: onChanged,
  67. onConfirm: onConfirm,
  68. locale: locale,
  69. theme: Theme.of(context, shadowThemeOnly: true),
  70. barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
  71. pickerModel: DateTimePickerModel(currentTime: currentTime, locale: locale)));
  72. }
  73. ///
  74. /// Display date picker bottom sheet witch custom picker model.
  75. ///
  76. static void showPicker(BuildContext context,
  77. {bool showTitleActions: true,
  78. DateChangedCallback onChanged,
  79. DateChangedCallback onConfirm,
  80. locale: 'en',
  81. BasePickerModel pickerModel}) {
  82. Navigator.push(
  83. context,
  84. new _DatePickerRoute(
  85. showTitleActions: showTitleActions,
  86. onChanged: onChanged,
  87. onConfirm: onConfirm,
  88. locale: locale,
  89. theme: Theme.of(context, shadowThemeOnly: true),
  90. barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
  91. pickerModel: pickerModel));
  92. }
  93. }
  94. class _DatePickerRoute<T> extends PopupRoute<T> {
  95. _DatePickerRoute({
  96. this.showTitleActions,
  97. this.onChanged,
  98. this.onConfirm,
  99. this.theme,
  100. this.barrierLabel,
  101. this.locale,
  102. RouteSettings settings,
  103. pickerModel,
  104. }) : this.pickerModel = pickerModel ?? DatePickerModel(),
  105. super(settings: settings);
  106. final bool showTitleActions;
  107. final DateChangedCallback onChanged;
  108. final DateChangedCallback onConfirm;
  109. final ThemeData theme;
  110. final String locale;
  111. final BasePickerModel pickerModel;
  112. @override
  113. Duration get transitionDuration => const Duration(milliseconds: 200);
  114. @override
  115. bool get barrierDismissible => true;
  116. @override
  117. final String barrierLabel;
  118. @override
  119. Color get barrierColor => Colors.black54;
  120. AnimationController _animationController;
  121. @override
  122. AnimationController createAnimationController() {
  123. assert(_animationController == null);
  124. _animationController = BottomSheet.createAnimationController(navigator.overlay);
  125. return _animationController;
  126. }
  127. @override
  128. Widget buildPage(
  129. BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
  130. Widget bottomSheet = new MediaQuery.removePadding(
  131. context: context,
  132. removeTop: true,
  133. child: _DatePickerComponent(
  134. onChanged: onChanged,
  135. locale: this.locale,
  136. route: this,
  137. pickerModel: pickerModel,
  138. ),
  139. );
  140. if (theme != null) {
  141. bottomSheet = new Theme(data: theme, child: bottomSheet);
  142. }
  143. return bottomSheet;
  144. }
  145. }
  146. class _DatePickerComponent extends StatefulWidget {
  147. _DatePickerComponent(
  148. {Key key, @required this.route, this.onChanged, this.locale, this.pickerModel});
  149. final DateChangedCallback onChanged;
  150. final _DatePickerRoute route;
  151. final String locale;
  152. final BasePickerModel pickerModel;
  153. @override
  154. State<StatefulWidget> createState() {
  155. return _DatePickerState();
  156. }
  157. }
  158. class _DatePickerState extends State<_DatePickerComponent> {
  159. FixedExtentScrollController leftScrollCtrl, middleScrollCtrl, rightScrollCtrl;
  160. @override
  161. void initState() {
  162. super.initState();
  163. leftScrollCtrl =
  164. new FixedExtentScrollController(initialItem: widget.pickerModel.currentLeftIndex());
  165. middleScrollCtrl =
  166. new FixedExtentScrollController(initialItem: widget.pickerModel.currentMiddleIndex());
  167. rightScrollCtrl =
  168. new FixedExtentScrollController(initialItem: widget.pickerModel.currentRightIndex());
  169. }
  170. @override
  171. Widget build(BuildContext context) {
  172. return new GestureDetector(
  173. child: new AnimatedBuilder(
  174. animation: widget.route.animation,
  175. builder: (BuildContext context, Widget child) {
  176. return new ClipRect(
  177. child: new CustomSingleChildLayout(
  178. delegate: new _BottomPickerLayout(widget.route.animation.value,
  179. showTitleActions: widget.route.showTitleActions),
  180. child: new GestureDetector(
  181. child: Material(
  182. color: Colors.transparent,
  183. child: _renderPickerView(),
  184. ),
  185. ),
  186. ),
  187. );
  188. },
  189. ),
  190. );
  191. }
  192. void _notifyDateChanged() {
  193. if (widget.onChanged != null) {
  194. widget.onChanged(widget.pickerModel.finalTime());
  195. }
  196. }
  197. Widget _renderPickerView() {
  198. Widget itemView = _renderItemView();
  199. if (widget.route.showTitleActions) {
  200. return Column(
  201. children: <Widget>[
  202. _renderTitleActionsView(),
  203. itemView,
  204. ],
  205. );
  206. }
  207. return itemView;
  208. }
  209. Widget _renderColumnView(StringAtIndexCallBack stringAtIndexCB, ScrollController scrollController,
  210. int layoutProportion, ValueChanged<int> selectedChanged) {
  211. return Expanded(
  212. flex: layoutProportion,
  213. child: Container(
  214. padding: EdgeInsets.all(8.0),
  215. height: _kDatePickerHeight,
  216. decoration: BoxDecoration(color: Colors.white),
  217. child: CupertinoPicker.builder(
  218. backgroundColor: Colors.white,
  219. scrollController: scrollController,
  220. itemExtent: _kDatePickerItemHeight,
  221. onSelectedItemChanged: (int index) {
  222. selectedChanged(index);
  223. },
  224. useMagnifier: true,
  225. itemBuilder: (BuildContext context, int index) {
  226. final content = stringAtIndexCB(index);
  227. if (content == null) {
  228. return null;
  229. }
  230. return Container(
  231. height: _kDatePickerItemHeight,
  232. alignment: Alignment.center,
  233. child: Text(
  234. content,
  235. style: TextStyle(color: Color(0xFF000046), fontSize: _kDatePickerFontSize),
  236. textAlign: TextAlign.start,
  237. ),
  238. );
  239. })),
  240. );
  241. }
  242. Widget _renderItemView() {
  243. return Container(
  244. color: Colors.white,
  245. child: Row(
  246. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  247. children: <Widget>[
  248. _renderColumnView(widget.pickerModel.leftStringAtIndex, leftScrollCtrl,
  249. widget.pickerModel.layoutProportions()[0], (index) {
  250. widget.pickerModel.setLeftIndex(index);
  251. _notifyDateChanged();
  252. }),
  253. Text(
  254. widget.pickerModel.leftDivider(),
  255. style: TextStyle(color: Color(0xFF000046), fontSize: _kDatePickerFontSize),
  256. ),
  257. _renderColumnView(widget.pickerModel.middleStringAtIndex, middleScrollCtrl,
  258. widget.pickerModel.layoutProportions()[1], (index) {
  259. widget.pickerModel.setMiddleIndex(index);
  260. _notifyDateChanged();
  261. }),
  262. Text(
  263. widget.pickerModel.rightDivider(),
  264. style: TextStyle(color: Color(0xFF000046), fontSize: _kDatePickerFontSize),
  265. ),
  266. _renderColumnView(widget.pickerModel.rightStringAtIndex, rightScrollCtrl,
  267. widget.pickerModel.layoutProportions()[2], (index) {
  268. widget.pickerModel.setRightIndex(index);
  269. _notifyDateChanged();
  270. }),
  271. ],
  272. ),
  273. );
  274. }
  275. // Title View
  276. Widget _renderTitleActionsView() {
  277. String done = _localeDone();
  278. String cancel = _localeCancel();
  279. return Container(
  280. height: _kDatePickerTitleHeight,
  281. decoration: BoxDecoration(color: Colors.white),
  282. child: Row(
  283. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  284. children: <Widget>[
  285. Container(
  286. height: _kDatePickerTitleHeight,
  287. child: FlatButton(
  288. child: Text(
  289. '$cancel',
  290. style: TextStyle(
  291. color: Theme.of(context).unselectedWidgetColor,
  292. fontSize: 16.0,
  293. ),
  294. ),
  295. onPressed: () => Navigator.pop(context),
  296. ),
  297. ),
  298. Container(
  299. height: _kDatePickerTitleHeight,
  300. child: FlatButton(
  301. child: Text(
  302. '$done',
  303. style: TextStyle(
  304. color: Theme.of(context).primaryColor,
  305. fontSize: 16.0,
  306. ),
  307. ),
  308. onPressed: () {
  309. if (widget.route.onConfirm != null) {
  310. widget.route.onConfirm(widget.pickerModel.finalTime());
  311. }
  312. Navigator.pop(context);
  313. },
  314. ),
  315. ),
  316. ],
  317. ),
  318. );
  319. }
  320. String _localeDone() {
  321. if (widget.locale.matchAsPrefix('cn') == null) {
  322. return 'Done';
  323. } else {
  324. return '确定';
  325. }
  326. }
  327. String _localeCancel() {
  328. if (widget.locale.matchAsPrefix('cn') == null) {
  329. return 'Cancel';
  330. } else {
  331. return '取消';
  332. }
  333. }
  334. }
  335. class _BottomPickerLayout extends SingleChildLayoutDelegate {
  336. _BottomPickerLayout(this.progress, {this.itemCount, this.showTitleActions});
  337. final double progress;
  338. final int itemCount;
  339. final bool showTitleActions;
  340. @override
  341. BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
  342. double maxHeight = _kDatePickerHeight;
  343. if (showTitleActions) {
  344. maxHeight += _kDatePickerTitleHeight;
  345. }
  346. return new BoxConstraints(
  347. minWidth: constraints.maxWidth,
  348. maxWidth: constraints.maxWidth,
  349. minHeight: 0.0,
  350. maxHeight: maxHeight);
  351. }
  352. @override
  353. Offset getPositionForChild(Size size, Size childSize) {
  354. double height = size.height - childSize.height * progress;
  355. return new Offset(0.0, height);
  356. }
  357. @override
  358. bool shouldRelayout(_BottomPickerLayout oldDelegate) {
  359. return progress != oldDelegate.progress;
  360. }
  361. }