123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742 |
- // Copyright 2014 The Flutter Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- // ignore_for_file: implementation_imports
- import 'dart:math' as math;
- import 'package:flutter/material.dart';
- import 'package:flutter/scheduler.dart';
- import 'package:flutter/src/cupertino/picker.dart';
- // Values derived from https://developer.apple.com/design/resources/ and on iOS
- // simulators with "Debug View Hierarchy".
- const double _kItemExtent = 56.0;
- // From the picker's intrinsic content size constraint.
- const double _kPickerWidth = 380.0;
- const bool _kUseMagnifier = true;
- // const double _kMagnification = 2.35 / 2.1;
- const double _kMagnification = 1.06;
- const double _kDatePickerPadSize = 12.0;
- // The density of a date picker is different from a generic picker.
- // Eyeballed from iOS.
- const double _kSqueeze = 1.25;
- const TextStyle _kDefaultPickerTextStyle = TextStyle(
- letterSpacing: -0.83,
- );
- TextStyle _themeTextStyle(BuildContext context, {bool isValid = true}) {
- return isValid
- ? const TextStyle(color: Colors.black, fontSize: 24)
- : TextStyle(color: Colors.grey[700], fontSize: 22);
- }
- void _animateColumnControllerToItem(
- FixedExtentScrollController controller, int targetItem) {
- controller.animateToItem(
- targetItem,
- curve: Curves.easeInOut,
- duration: const Duration(milliseconds: 200),
- );
- }
- const Widget _startSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(
- capEndEdge: false,
- background: Colors.transparent,
- );
- const Widget _centerSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(
- capStartEdge: false,
- capEndEdge: false,
- background: Colors.transparent,
- );
- const Widget _endSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(
- capStartEdge: false,
- background: Colors.transparent,
- );
- // Lays out the date picker based on how much space each single column needs.
- //
- // Each column is a child of this delegate, indexed from 0 to number of columns - 1.
- // Each column will be padded horizontally by 12.0 both left and right.
- //
- // The picker will be placed in the center, and the leftmost and rightmost
- // column will be extended equally to the remaining width.
- class _DatePickerLayoutDelegate extends MultiChildLayoutDelegate {
- _DatePickerLayoutDelegate({
- required this.columnWidths,
- required this.textDirectionFactor,
- required this.maxWidth,
- });
- // The list containing widths of all columns.
- final List<double> columnWidths;
- // textDirectionFactor is 1 if text is written left to right, and -1 if right to left.
- final int textDirectionFactor;
- // The max width the children should reach to avoid bending outwards.
- final double maxWidth;
- @override
- void performLayout(Size size) {
- double remainingWidth = maxWidth < size.width ? maxWidth : size.width;
- double currentHorizontalOffset = (size.width - remainingWidth) / 2;
- for (int i = 0; i < columnWidths.length; i++) {
- remainingWidth -= columnWidths[i] + _kDatePickerPadSize * 2;
- }
- for (int i = 0; i < columnWidths.length; i++) {
- final int index =
- textDirectionFactor == 1 ? i : columnWidths.length - i - 1;
- double childWidth = columnWidths[index] + _kDatePickerPadSize * 2;
- if (index == 0 || index == columnWidths.length - 1) {
- childWidth += remainingWidth / 2;
- }
- // We can't actually assert here because it would break things badly for
- // semantics, which will expect that we laid things out here.
- assert(() {
- if (childWidth < 0) {
- FlutterError.reportError(
- FlutterErrorDetails(
- exception: FlutterError(
- 'Insufficient horizontal space to render the '
- 'CupertinoDatePicker because the parent is too narrow at '
- '${size.width}px.\n'
- 'An additional ${-remainingWidth}px is needed to avoid '
- 'overlapping columns.',
- ),
- ),
- );
- }
- return true;
- }());
- layoutChild(index,
- BoxConstraints.tight(Size(math.max(0.0, childWidth), size.height)));
- positionChild(index, Offset(currentHorizontalOffset, 0.0));
- currentHorizontalOffset += childWidth;
- }
- }
- @override
- bool shouldRelayout(_DatePickerLayoutDelegate oldDelegate) {
- return columnWidths != oldDelegate.columnWidths ||
- textDirectionFactor != oldDelegate.textDirectionFactor;
- }
- }
- /// Different display modes of [CupertinoDatePicker].
- ///
- /// See also:
- ///
- /// * [CupertinoDatePicker], the class that implements different display modes
- /// of the iOS-style date picker.
- /// * [CupertinoPicker], the class that implements a content agnostic spinner UI.
- enum CupertinoDatePickerMode {
- /// Mode that shows the date in hour, minute, and (optional) an AM/PM designation.
- /// The AM/PM designation is shown only if [CupertinoDatePicker] does not use 24h format.
- /// Column order is subject to internationalization.
- ///
- /// Example: ` 4 | 14 | PM `.
- time,
- /// Mode that shows the date in month, day of month, and year.
- /// Name of month is spelled in full.
- /// Column order is subject to internationalization.
- ///
- /// Example: ` July | 13 | 2012 `.
- date,
- /// Mode that shows the date as day of the week, month, day of month and
- /// the time in hour, minute, and (optional) an AM/PM designation.
- /// The AM/PM designation is shown only if [CupertinoDatePicker] does not use 24h format.
- /// Column order is subject to internationalization.
- ///
- /// Example: ` Fri Jul 13 | 4 | 14 | PM `
- dateAndTime,
- }
- // Different types of column in CupertinoDatePicker.
- enum _PickerColumnType {
- // Day of month column in date mode.
- dayOfMonth,
- // Month column in date mode.
- month,
- // Year column in date mode.
- year,
- }
- /// A date picker widget in iOS style.
- ///
- /// There are several modes of the date picker listed in [CupertinoDatePickerMode].
- ///
- /// The class will display its children as consecutive columns. Its children
- /// order is based on internationalization, or the [dateOrder] property if specified.
- ///
- /// Example of the picker in date mode:
- ///
- /// * US-English: `| July | 13 | 2012 |`
- /// * Vietnamese: `| 13 | Tháng 7 | 2012 |`
- ///
- /// Can be used with [showCupertinoModalPopup] to display the picker modally at
- /// the bottom of the screen.
- ///
- /// Sizes itself to its parent and may not render correctly if not given the
- /// full screen width. Content texts are shown with
- /// [CupertinoTextThemeData.dateTimePickerTextStyle].
- ///
- /// {@tool dartpad}
- /// This sample shows how to implement CupertinoDatePicker with different picker modes.
- /// We can provide initial dateTime value for the picker to display. When user changes
- /// the drag the date or time wheels, the picker will call onDateTimeChanged callback.
- ///
- /// CupertinoDatePicker can be displayed directly on a screen or in a popup.
- ///
- /// ** See code in examples/api/lib/cupertino/date_picker/cupertino_date_picker.0.dart **
- /// {@end-tool}
- ///
- /// See also:
- ///
- /// * [CupertinoTimerPicker], the class that implements the iOS-style timer picker.
- /// * [CupertinoPicker], the class that implements a content agnostic spinner UI.
- /// * <https://developer.apple.com/design/human-interface-guidelines/ios/controls/pickers/>
- class VCustomCupertinoDatePicker extends StatefulWidget {
- /// Constructs an iOS style date picker.
- ///
- /// [mode] is one of the mode listed in [CupertinoDatePickerMode] and defaults
- /// to [CupertinoDatePickerMode.dateAndTime].
- ///
- /// [onDateTimeChanged] is the callback called when the selected date or time
- /// changes and must not be null. When in [CupertinoDatePickerMode.time] mode,
- /// the year, month and day will be the same as [initialDateTime]. When in
- /// [CupertinoDatePickerMode.date] mode, this callback will always report the
- /// start time of the currently selected day.
- ///
- /// [initialDateTime] is the initial date time of the picker. Defaults to the
- /// present date and time and must not be null. The present must conform to
- /// the intervals set in [minimumDate], [maximumDate], [minimumYear], and
- /// [maximumYear].
- ///
- /// [minimumDate] is the minimum selectable [DateTime] of the picker. When set
- /// to null, the picker does not limit the minimum [DateTime] the user can pick.
- /// In [CupertinoDatePickerMode.time] mode, [minimumDate] should typically be
- /// on the same date as [initialDateTime], as the picker will not limit the
- /// minimum time the user can pick if it's set to a date earlier than that.
- ///
- /// [maximumDate] is the maximum selectable [DateTime] of the picker. When set
- /// to null, the picker does not limit the maximum [DateTime] the user can pick.
- /// In [CupertinoDatePickerMode.time] mode, [maximumDate] should typically be
- /// on the same date as [initialDateTime], as the picker will not limit the
- /// maximum time the user can pick if it's set to a date later than that.
- ///
- /// [minimumYear] is the minimum year that the picker can be scrolled to in
- /// [CupertinoDatePickerMode.date] mode. Defaults to 1 and must not be null.
- ///
- /// [maximumYear] is the maximum year that the picker can be scrolled to in
- /// [CupertinoDatePickerMode.date] mode. Null if there's no limit.
- ///
- /// [minuteInterval] is the granularity of the minute spinner. Must be a
- /// positive integer factor of 60.
- ///
- /// [use24hFormat] decides whether 24 hour format is used. Defaults to false.
- ///
- /// [dateOrder] determines the order of the columns inside [CupertinoDatePicker] in date mode.
- /// Defaults to the locale's default date format/order.
- VCustomCupertinoDatePicker({
- super.key,
- required this.onDateTimeChanged,
- DateTime? initialDateTime,
- this.minimumDate,
- this.maximumDate,
- this.minimumYear = 1,
- this.maximumYear,
- this.minuteInterval = 1,
- this.backgroundColor,
- }) : initialDateTime = initialDateTime ?? DateTime.now(),
- assert(
- minuteInterval > 0 && 60 % minuteInterval == 0,
- 'minute interval is not a positive integer factor of 60',
- ) {
- assert(
- this.initialDateTime.minute % minuteInterval == 0,
- 'initial minute is not divisible by minute interval',
- );
- }
- /// The initial date and/or time of the picker. Defaults to the present date
- /// and time and must not be null. The present must conform to the intervals
- /// set in [minimumDate], [maximumDate], [minimumYear], and [maximumYear].
- ///
- /// Changing this value after the initial build will not affect the currently
- /// selected date time.
- final DateTime initialDateTime;
- /// The minimum selectable date that the picker can settle on.
- ///
- /// When non-null, the user can still scroll the picker to [DateTime]s earlier
- /// than [minimumDate], but the [onDateTimeChanged] will not be called on
- /// these [DateTime]s. Once let go, the picker will scroll back to [minimumDate].
- ///
- /// In [CupertinoDatePickerMode.time] mode, a time becomes unselectable if the
- /// [DateTime] produced by combining that particular time and the date part of
- /// [initialDateTime] is earlier than [minimumDate]. So typically [minimumDate]
- /// needs to be set to a [DateTime] that is on the same date as [initialDateTime].
- ///
- /// Defaults to null. When set to null, the picker does not impose a limit on
- /// the earliest [DateTime] the user can select.
- final DateTime? minimumDate;
- /// The maximum selectable date that the picker can settle on.
- ///
- /// When non-null, the user can still scroll the picker to [DateTime]s later
- /// than [maximumDate], but the [onDateTimeChanged] will not be called on
- /// these [DateTime]s. Once let go, the picker will scroll back to [maximumDate].
- ///
- /// In [CupertinoDatePickerMode.time] mode, a time becomes unselectable if the
- /// [DateTime] produced by combining that particular time and the date part of
- /// [initialDateTime] is later than [maximumDate]. So typically [maximumDate]
- /// needs to be set to a [DateTime] that is on the same date as [initialDateTime].
- ///
- /// Defaults to null. When set to null, the picker does not impose a limit on
- /// the latest [DateTime] the user can select.
- final DateTime? maximumDate;
- /// Minimum year that the picker can be scrolled to in
- /// [CupertinoDatePickerMode.date] mode. Defaults to 1 and must not be null.
- final int minimumYear;
- /// Maximum year that the picker can be scrolled to in
- /// [CupertinoDatePickerMode.date] mode. Null if there's no limit.
- final int? maximumYear;
- /// The granularity of the minutes spinner, if it is shown in the current mode.
- /// Must be an integer factor of 60.
- final int minuteInterval;
- /// Callback called when the selected date and/or time changes. If the new
- /// selected [DateTime] is not valid, or is not in the [minimumDate] through
- /// [maximumDate] range, this callback will not be called.
- ///
- /// Must not be null.
- final ValueChanged<DateTime> onDateTimeChanged;
- /// Background color of date picker.
- ///
- /// Defaults to null, which disables background painting entirely.
- final Color? backgroundColor;
- @override
- State<StatefulWidget> createState() =>
- // ignore: no_logic_in_create_state
- _CupertinoDatePickerDateState();
- // Estimate the minimum width that each column needs to layout its content.
- static double _getColumnWidth(
- _PickerColumnType columnType, BuildContext context) {
- switch (columnType) {
- case _PickerColumnType.year:
- return 132;
- case _PickerColumnType.month:
- return 90;
- case _PickerColumnType.dayOfMonth:
- return 110;
- default:
- return 0;
- }
- }
- }
- typedef _ColumnBuilder = Widget Function(double offAxisFraction,
- TransitionBuilder itemPositioningBuilder, Widget selectionOverlay);
- class _CupertinoDatePickerDateState extends State<VCustomCupertinoDatePicker> {
- late int textDirectionFactor;
- // Alignment based on text direction. The variable name is self descriptive,
- // however, when text direction is rtl, alignment is reversed.
- late Alignment alignCenterLeft;
- late Alignment alignCenterRight;
- // The currently selected values of the picker.
- late int selectedDay;
- late int selectedMonth;
- late int selectedYear;
- // The controller of the day picker. There are cases where the selected value
- // of the picker is invalid (e.g. February 30th 2018), and this dayController
- // is responsible for jumping to a valid value.
- late FixedExtentScrollController dayController;
- late FixedExtentScrollController monthController;
- late FixedExtentScrollController yearController;
- bool isDayPickerScrolling = false;
- bool isMonthPickerScrolling = false;
- bool isYearPickerScrolling = false;
- bool get isScrolling =>
- isDayPickerScrolling || isMonthPickerScrolling || isYearPickerScrolling;
- // Estimated width of columns.
- Map<int, double> estimatedColumnWidths = <int, double>{};
- @override
- void initState() {
- super.initState();
- selectedDay = widget.initialDateTime.day;
- selectedMonth = widget.initialDateTime.month;
- selectedYear = widget.initialDateTime.year;
- dayController = FixedExtentScrollController(initialItem: selectedDay - 1);
- monthController =
- FixedExtentScrollController(initialItem: selectedMonth - 1);
- yearController = FixedExtentScrollController(initialItem: selectedYear);
- PaintingBinding.instance.systemFonts.addListener(_handleSystemFontsChange);
- }
- void _handleSystemFontsChange() {
- setState(() {
- // System fonts change might cause the text layout width to change.
- _refreshEstimatedColumnWidths();
- });
- }
- @override
- void dispose() {
- dayController.dispose();
- monthController.dispose();
- yearController.dispose();
- PaintingBinding.instance.systemFonts
- .removeListener(_handleSystemFontsChange);
- super.dispose();
- }
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- textDirectionFactor =
- Directionality.of(context) == TextDirection.ltr ? 1 : -1;
- alignCenterLeft =
- textDirectionFactor == 1 ? Alignment.centerLeft : Alignment.centerRight;
- alignCenterRight =
- textDirectionFactor == 1 ? Alignment.centerRight : Alignment.centerLeft;
- _refreshEstimatedColumnWidths();
- }
- void _refreshEstimatedColumnWidths() {
- estimatedColumnWidths[_PickerColumnType.dayOfMonth.index] =
- VCustomCupertinoDatePicker._getColumnWidth(
- _PickerColumnType.dayOfMonth, context);
- estimatedColumnWidths[_PickerColumnType.month.index] =
- VCustomCupertinoDatePicker._getColumnWidth(
- _PickerColumnType.month, context);
- estimatedColumnWidths[_PickerColumnType.year.index] =
- VCustomCupertinoDatePicker._getColumnWidth(
- _PickerColumnType.year, context);
- }
- // The DateTime of the last day of a given month in a given year.
- // Let `DateTime` handle the year/month overflow.
- DateTime _lastDayInMonth(int year, int month) => DateTime(year, month + 1, 0);
- Widget _buildDayPicker(double offAxisFraction,
- TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
- final int daysInCurrentMonth =
- _lastDayInMonth(selectedYear, selectedMonth).day;
- return NotificationListener<ScrollNotification>(
- onNotification: (ScrollNotification notification) {
- if (notification is ScrollStartNotification) {
- isDayPickerScrolling = true;
- } else if (notification is ScrollEndNotification) {
- isDayPickerScrolling = false;
- _pickerDidStopScrolling();
- }
- return false;
- },
- child: CupertinoPicker(
- scrollController: dayController,
- // offAxisFraction: offAxisFraction,
- itemExtent: _kItemExtent,
- useMagnifier: _kUseMagnifier,
- magnification: _kMagnification,
- backgroundColor: widget.backgroundColor,
- squeeze: _kSqueeze,
- onSelectedItemChanged: (int index) {
- selectedDay = index + 1;
- if (_isCurrentDateValid) {
- widget.onDateTimeChanged(
- DateTime(selectedYear, selectedMonth, selectedDay));
- }
- },
- looping: true,
- selectionOverlay: selectionOverlay,
- children: List<Widget>.generate(31, (int index) {
- final int day = index + 1;
- return itemPositioningBuilder(
- context,
- Text(
- "$day日",
- style:
- _themeTextStyle(context, isValid: day <= daysInCurrentMonth),
- ),
- );
- }),
- ),
- );
- }
- Widget _buildMonthPicker(double offAxisFraction,
- TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
- return NotificationListener<ScrollNotification>(
- onNotification: (ScrollNotification notification) {
- if (notification is ScrollStartNotification) {
- isMonthPickerScrolling = true;
- } else if (notification is ScrollEndNotification) {
- isMonthPickerScrolling = false;
- _pickerDidStopScrolling();
- }
- return false;
- },
- child: CupertinoPicker(
- scrollController: monthController,
- // offAxisFraction: offAxisFraction,
- itemExtent: _kItemExtent,
- useMagnifier: _kUseMagnifier,
- magnification: _kMagnification,
- backgroundColor: widget.backgroundColor,
- squeeze: _kSqueeze,
- onSelectedItemChanged: (int index) {
- selectedMonth = index + 1;
- if (_isCurrentDateValid) {
- widget.onDateTimeChanged(
- DateTime(selectedYear, selectedMonth, selectedDay));
- }
- },
- looping: true,
- selectionOverlay: selectionOverlay,
- children: List<Widget>.generate(12, (int index) {
- final int month = index + 1;
- final bool isInvalidMonth =
- (widget.minimumDate?.year == selectedYear &&
- widget.minimumDate!.month > month) ||
- (widget.maximumDate?.year == selectedYear &&
- widget.maximumDate!.month < month);
- return itemPositioningBuilder(
- context,
- Text(
- "$month月",
- style: _themeTextStyle(context, isValid: !isInvalidMonth),
- ),
- );
- }),
- ),
- );
- }
- Widget _buildYearPicker(double offAxisFraction,
- TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
- return NotificationListener<ScrollNotification>(
- onNotification: (ScrollNotification notification) {
- if (notification is ScrollStartNotification) {
- isYearPickerScrolling = true;
- } else if (notification is ScrollEndNotification) {
- isYearPickerScrolling = false;
- _pickerDidStopScrolling();
- }
- return false;
- },
- child: CupertinoPicker.builder(
- scrollController: yearController,
- itemExtent: _kItemExtent,
- // offAxisFraction: offAxisFraction,
- useMagnifier: _kUseMagnifier,
- magnification: _kMagnification,
- backgroundColor: widget.backgroundColor,
- onSelectedItemChanged: (int index) {
- selectedYear = index;
- if (_isCurrentDateValid) {
- widget.onDateTimeChanged(
- DateTime(selectedYear, selectedMonth, selectedDay));
- }
- },
- itemBuilder: (BuildContext context, int year) {
- if (year < widget.minimumYear) {
- return null;
- }
- if (widget.maximumYear != null && year > widget.maximumYear!) {
- return null;
- }
- final bool isValidYear = (widget.minimumDate == null ||
- widget.minimumDate!.year <= year) &&
- (widget.maximumDate == null || widget.maximumDate!.year >= year);
- return itemPositioningBuilder(
- context,
- Text(
- "$year年",
- style: _themeTextStyle(context, isValid: isValidYear),
- ),
- );
- },
- selectionOverlay: selectionOverlay,
- ),
- );
- }
- bool get _isCurrentDateValid {
- // The current date selection represents a range [minSelectedData, maxSelectDate].
- final DateTime minSelectedDate =
- DateTime(selectedYear, selectedMonth, selectedDay);
- final DateTime maxSelectedDate =
- DateTime(selectedYear, selectedMonth, selectedDay + 1);
- final bool minCheck = widget.minimumDate?.isBefore(maxSelectedDate) ?? true;
- final bool maxCheck =
- widget.maximumDate?.isBefore(minSelectedDate) ?? false;
- return minCheck && !maxCheck && minSelectedDate.day == selectedDay;
- }
- // One or more pickers have just stopped scrolling.
- void _pickerDidStopScrolling() {
- // Call setState to update the greyed out days/months/years, as the currently
- // selected year/month may have changed.
- setState(() {});
- if (isScrolling) {
- return;
- }
- // Whenever scrolling lands on an invalid entry, the picker
- // automatically scrolls to a valid one.
- final DateTime minSelectDate =
- DateTime(selectedYear, selectedMonth, selectedDay);
- final DateTime maxSelectDate =
- DateTime(selectedYear, selectedMonth, selectedDay + 1);
- final bool minCheck = widget.minimumDate?.isBefore(maxSelectDate) ?? true;
- final bool maxCheck = widget.maximumDate?.isBefore(minSelectDate) ?? false;
- if (!minCheck || maxCheck) {
- // We have minCheck === !maxCheck.
- final DateTime targetDate =
- minCheck ? widget.maximumDate! : widget.minimumDate!;
- _scrollToDate(targetDate);
- return;
- }
- // Some months have less days (e.g. February). Go to the last day of that month
- // if the selectedDay exceeds the maximum.
- if (minSelectDate.day != selectedDay) {
- final DateTime lastDay = _lastDayInMonth(selectedYear, selectedMonth);
- _scrollToDate(lastDay);
- }
- }
- void _scrollToDate(DateTime newDate) {
- SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
- if (selectedYear != newDate.year) {
- _animateColumnControllerToItem(yearController, newDate.year);
- }
- if (selectedMonth != newDate.month) {
- _animateColumnControllerToItem(monthController, newDate.month - 1);
- }
- if (selectedDay != newDate.day) {
- _animateColumnControllerToItem(dayController, newDate.day - 1);
- }
- });
- }
- @override
- Widget build(BuildContext context) {
- final List<_ColumnBuilder> pickerBuilders = <_ColumnBuilder>[
- _buildYearPicker,
- _buildMonthPicker,
- _buildDayPicker,
- ];
- final List<double> columnWidths = <double>[
- estimatedColumnWidths[_PickerColumnType.year.index]!,
- estimatedColumnWidths[_PickerColumnType.month.index]!,
- estimatedColumnWidths[_PickerColumnType.dayOfMonth.index]!,
- ];
- final List<Widget> pickers = <Widget>[];
- double totalColumnWidths = 4 * _kDatePickerPadSize;
- for (int i = 0; i < columnWidths.length; i++) {
- final double offAxisFraction = (i - 1) * 0.3 * textDirectionFactor;
- EdgeInsets padding = const EdgeInsets.only(right: _kDatePickerPadSize);
- if (textDirectionFactor == -1) {
- padding = const EdgeInsets.only(left: _kDatePickerPadSize);
- }
- Widget selectionOverlay = _centerSelectionOverlay;
- if (i == 0) {
- selectionOverlay = _startSelectionOverlay;
- } else if (i == columnWidths.length - 1) {
- selectionOverlay = _endSelectionOverlay;
- }
- totalColumnWidths += columnWidths[i] + (2 * _kDatePickerPadSize);
- pickers.add(LayoutId(
- id: i,
- child: pickerBuilders[i](
- offAxisFraction,
- (BuildContext context, Widget? child) {
- return Container(
- alignment: i == columnWidths.length - 1
- ? alignCenterLeft
- : alignCenterRight,
- padding: i == 0 ? null : padding,
- child: Container(
- alignment: i == 0 ? alignCenterLeft : alignCenterRight,
- width: columnWidths[i] + _kDatePickerPadSize,
- child: child,
- ),
- );
- },
- selectionOverlay,
- ),
- ));
- }
- final double maxPickerWidth =
- totalColumnWidths > _kPickerWidth ? totalColumnWidths : _kPickerWidth;
- return MediaQuery(
- data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
- child: DefaultTextStyle.merge(
- style: _kDefaultPickerTextStyle,
- child: CustomMultiChildLayout(
- delegate: _DatePickerLayoutDelegate(
- columnWidths: columnWidths,
- textDirectionFactor: textDirectionFactor,
- maxWidth: maxPickerWidth,
- ),
- children: pickers,
- ),
- ),
- );
- }
- }
|