siyao
4 年前
当前提交
0f753083
共有 9 个文件被更改,包括 1017 次插入 和 2 次删除
-
2com.unity.uiwidgets/Runtime/material/dropdown.cs
-
2com.unity.uiwidgets/Runtime/material/material_state.cs
-
928com.unity.uiwidgets/Runtime/material/picker/calendar_date_picker.cs
-
3com.unity.uiwidgets/Runtime/material/picker/calendar_date_picker.cs.meta
-
20com.unity.uiwidgets/Runtime/material/picker/date_picker_common.cs
-
3com.unity.uiwidgets/Runtime/material/picker/date_picker_common.cs.meta
-
58com.unity.uiwidgets/Runtime/material/picker/date_utils.cs
-
3com.unity.uiwidgets/Runtime/material/picker/date_utils.cs.meta
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using com.unity.uiwidgets.Runtime.rendering; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine; |
|||
using Color = Unity.UIWidgets.ui.Color; |
|||
using TextStyle = Unity.UIWidgets.painting.TextStyle; |
|||
|
|||
namespace Unity.UIWidgets.material { |
|||
public partial class material_ { |
|||
public static readonly TimeSpan _monthScrollDuration = new TimeSpan(0, 0, 0, 0, 200); |
|||
|
|||
public const float _dayPickerRowHeight = 42.0f; |
|||
|
|||
public const int _maxDayPickerRowCount = 6; // A 31 day month that starts on Saturday.
|
|||
|
|||
public const float _maxDayPickerHeight = _dayPickerRowHeight * (_maxDayPickerRowCount + 1); |
|||
public const float _monthPickerHorizontalPadding = 8.0f; |
|||
|
|||
public const int _yearPickerColumnCount = 3; |
|||
public const float _yearPickerPadding = 16.0f; |
|||
public const float _yearPickerRowHeight = 52.0f; |
|||
public const float _yearPickerRowSpacing = 8.0f; |
|||
|
|||
public const float _subHeaderHeight = 52.0f; |
|||
public const float _monthNavButtonsWidth = 108.0f; |
|||
} |
|||
|
|||
public class CalendarDatePicker : StatefulWidget { |
|||
public CalendarDatePicker( |
|||
Key key = null, |
|||
DateTime? initialDate = null, |
|||
DateTime? firstDate = null, |
|||
DateTime? lastDate = null, |
|||
ValueChanged<DateTime> onDateChanged = null, |
|||
ValueChanged<DateTime> onDisplayedMonthChanged = null, |
|||
DatePickerMode initialCalendarMode = DatePickerMode.day, |
|||
material_.SelectableDayPredicate selectableDayPredicate = null |
|||
) : base(key: key) { |
|||
D.assert(initialDate != null); |
|||
D.assert(firstDate != null); |
|||
D.assert(lastDate != null); |
|||
initialDate = utils.dateOnly(initialDate.Value); |
|||
firstDate = utils.dateOnly(firstDate.Value); |
|||
lastDate = utils.dateOnly(lastDate.Value); |
|||
D.assert(onDateChanged != null); |
|||
D.assert(initialCalendarMode != null); |
|||
|
|||
this.onDateChanged = onDateChanged; |
|||
this.onDisplayedMonthChanged = onDisplayedMonthChanged; |
|||
this.initialCalendarMode = initialCalendarMode; |
|||
this.selectableDayPredicate = selectableDayPredicate; |
|||
D.assert( |
|||
!(this.lastDate < this.firstDate), |
|||
() => $"lastDate {lastDate} must be on or after firstDate {firstDate}." |
|||
); |
|||
D.assert( |
|||
!(this.initialDate < this.firstDate), |
|||
() => $"initialDate {initialDate} must be on or after firstDate {firstDate}." |
|||
); |
|||
D.assert( |
|||
!(this.initialDate > this.lastDate), |
|||
() => $"initialDate {initialDate} must be on or before lastDate {lastDate}." |
|||
); |
|||
D.assert( |
|||
selectableDayPredicate == null || selectableDayPredicate(this.initialDate), |
|||
() => $"Provided initialDate {initialDate} must satisfy provided selectableDayPredicate." |
|||
); |
|||
} |
|||
|
|||
public readonly DateTime initialDate; |
|||
|
|||
public readonly DateTime firstDate; |
|||
|
|||
public readonly DateTime lastDate; |
|||
|
|||
public readonly ValueChanged<DateTime> onDateChanged; |
|||
|
|||
public readonly ValueChanged<DateTime> onDisplayedMonthChanged; |
|||
|
|||
public readonly DatePickerMode initialCalendarMode; |
|||
|
|||
public readonly material_.SelectableDayPredicate selectableDayPredicate; |
|||
|
|||
public override State createState() => new UIWidgets.material._CalendarDatePickerState(); |
|||
} |
|||
|
|||
internal class _CalendarDatePickerState : State<CalendarDatePicker> { |
|||
bool _announcedInitialDate = false; |
|||
DatePickerMode _mode; |
|||
DateTime _currentDisplayedMonthDate; |
|||
DateTime _selectedDate; |
|||
public readonly GlobalKey _monthPickerKey = GlobalKey.key(); |
|||
public readonly GlobalKey _yearPickerKey = GlobalKey.key(); |
|||
MaterialLocalizations _localizations; |
|||
TextDirection _textDirection; |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
_mode = widget.initialCalendarMode; |
|||
_currentDisplayedMonthDate = new DateTime(widget.initialDate.Year, widget.initialDate.Month, 1); |
|||
_selectedDate = widget.initialDate; |
|||
} |
|||
|
|||
public override void didChangeDependencies() { |
|||
base.didChangeDependencies(); |
|||
_localizations = MaterialLocalizations.of(context); |
|||
_textDirection = Directionality.of(context); |
|||
if (!_announcedInitialDate) { |
|||
_announcedInitialDate = true; |
|||
// SemanticsService.announce(
|
|||
// _localizations.formatFullDate(_selectedDate),
|
|||
// _textDirection,
|
|||
// );
|
|||
} |
|||
} |
|||
|
|||
void _vibrate() { |
|||
// switch (Theme.of(context).platform) {
|
|||
// case TargetPlatform.android:
|
|||
// case TargetPlatform.fuchsia:
|
|||
// case TargetPlatform.linux:
|
|||
// case TargetPlatform.windows:
|
|||
// // HapticFeedback.vibrate();
|
|||
// break;
|
|||
// case TargetPlatform.iOS:
|
|||
// case TargetPlatform.macOS:
|
|||
// break;
|
|||
// }
|
|||
} |
|||
|
|||
void _handleModeChanged(DatePickerMode mode) { |
|||
_vibrate(); |
|||
setState(() => { |
|||
_mode = mode; |
|||
// if (_mode == DatePickerMode.day) {
|
|||
// SemanticsService.announce(
|
|||
// _localizations.formatMonthYear(_selectedDate),
|
|||
// _textDirection,
|
|||
// );
|
|||
// } else {
|
|||
// SemanticsService.announce(
|
|||
// _localizations.formatYear(_selectedDate),
|
|||
// _textDirection,
|
|||
// );
|
|||
// }
|
|||
}); |
|||
} |
|||
|
|||
void _handleMonthChanged(DateTime date) { |
|||
setState(() => { |
|||
if (_currentDisplayedMonthDate.Year != date.Year || _currentDisplayedMonthDate.Month != date.Month) { |
|||
_currentDisplayedMonthDate = new DateTime(date.Year, date.Month, 1); |
|||
widget.onDisplayedMonthChanged?.Invoke(_currentDisplayedMonthDate); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
void _handleYearChanged(DateTime value) { |
|||
_vibrate(); |
|||
|
|||
if (value < widget.firstDate) { |
|||
value = widget.firstDate; |
|||
} |
|||
else if (value > widget.lastDate) { |
|||
value = widget.lastDate; |
|||
} |
|||
|
|||
setState(() => { |
|||
_mode = DatePickerMode.day; |
|||
_handleMonthChanged(value); |
|||
}); |
|||
} |
|||
|
|||
void _handleDayChanged(DateTime value) { |
|||
_vibrate(); |
|||
setState(() => { |
|||
_selectedDate = value; |
|||
widget.onDateChanged?.Invoke(_selectedDate); |
|||
}); |
|||
} |
|||
|
|||
Widget _buildPicker() { |
|||
D.assert(_mode != null); |
|||
switch (_mode) { |
|||
case DatePickerMode.day: |
|||
return new _MonthPicker( |
|||
key: _monthPickerKey, |
|||
initialMonth: _currentDisplayedMonthDate, |
|||
currentDate: DateTime.Now, |
|||
firstDate: widget.firstDate, |
|||
lastDate: widget.lastDate, |
|||
selectedDate: _selectedDate, |
|||
onChanged: _handleDayChanged, |
|||
onDisplayedMonthChanged: _handleMonthChanged, |
|||
selectableDayPredicate: widget.selectableDayPredicate |
|||
); |
|||
case DatePickerMode.year: |
|||
return new Padding( |
|||
padding: EdgeInsets.only(top: material_._subHeaderHeight), |
|||
child: new _YearPicker( |
|||
key: _yearPickerKey, |
|||
currentDate: DateTime.Now, |
|||
firstDate: widget.firstDate, |
|||
lastDate: widget.lastDate, |
|||
initialDate: _currentDisplayedMonthDate, |
|||
selectedDate: _selectedDate, |
|||
onChanged: _handleYearChanged |
|||
) |
|||
); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new Stack( |
|||
children: new List<Widget> { |
|||
new SingleChildScrollView( |
|||
child: new SizedBox( |
|||
height: material_._maxDayPickerHeight, |
|||
child: _buildPicker() |
|||
) |
|||
), |
|||
// Put the mode toggle button on top so that it won"t be covered up by the _MonthPicker
|
|||
new _DatePickerModeToggleButton( |
|||
mode: _mode, |
|||
title: _localizations.formatMonthYear(_currentDisplayedMonthDate), |
|||
onTitlePressed: () => { |
|||
// Toggle the day/year mode.
|
|||
_handleModeChanged(_mode == DatePickerMode.day ? DatePickerMode.year : DatePickerMode.day); |
|||
} |
|||
) |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _DatePickerModeToggleButton : StatefulWidget { |
|||
internal _DatePickerModeToggleButton( |
|||
DatePickerMode mode, |
|||
string title, |
|||
VoidCallback onTitlePressed |
|||
) { |
|||
this.mode = mode; |
|||
this.title = title; |
|||
this.onTitlePressed = onTitlePressed; |
|||
} |
|||
|
|||
public readonly DatePickerMode mode; |
|||
|
|||
public readonly string title; |
|||
|
|||
public readonly VoidCallback onTitlePressed; |
|||
|
|||
public override State createState() => new _DatePickerModeToggleButtonState(); |
|||
} |
|||
|
|||
class _DatePickerModeToggleButtonState : SingleTickerProviderStateMixin<_DatePickerModeToggleButton> { |
|||
AnimationController _controller; |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
_controller = new AnimationController( |
|||
value: widget.mode == DatePickerMode.year ? 0.5f : 0, |
|||
upperBound: 0.5f, |
|||
duration: new TimeSpan(0, 0, 0, 0, 200), |
|||
vsync: this |
|||
); |
|||
} |
|||
|
|||
public override void didUpdateWidget(StatefulWidget statefulWidget) { |
|||
base.didUpdateWidget(statefulWidget); |
|||
if (statefulWidget is _DatePickerModeToggleButton oldWidget) { |
|||
if (oldWidget.mode == widget.mode) { |
|||
return; |
|||
} |
|||
|
|||
if (widget.mode == DatePickerMode.year) { |
|||
_controller.forward(); |
|||
} |
|||
else { |
|||
_controller.reverse(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
ColorScheme colorScheme = Theme.of(context).colorScheme; |
|||
TextTheme textTheme = Theme.of(context).textTheme; |
|||
Color controlColor = colorScheme.onSurface.withOpacity(0.60f); |
|||
|
|||
var rowChildren = new List<Widget> { |
|||
new Flexible( |
|||
child: new Container( |
|||
height: material_._subHeaderHeight, |
|||
child: new InkWell( |
|||
onTap: () => widget.onTitlePressed(), |
|||
child: new Padding( |
|||
padding: EdgeInsets.symmetric(horizontal: 8), |
|||
child: new Row( |
|||
children: new List<Widget> { |
|||
new Flexible( |
|||
child: new Text( |
|||
widget.title, |
|||
overflow: TextOverflow.ellipsis, |
|||
style: textTheme.subtitle2?.copyWith( |
|||
color: controlColor |
|||
) |
|||
) |
|||
), |
|||
new RotationTransition( |
|||
turns: _controller, |
|||
child: new Icon( |
|||
Icons.arrow_drop_down, |
|||
color: controlColor |
|||
) |
|||
), |
|||
} |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
}; |
|||
if (widget.mode == DatePickerMode.day) { |
|||
// Give space for the prev/next month buttons that are underneath this row
|
|||
rowChildren.Add(new SizedBox(width: material_._monthNavButtonsWidth)); |
|||
} |
|||
|
|||
return new Container( |
|||
//TODO: update to EdgeInsetsGeometry
|
|||
padding: (EdgeInsets) (EdgeInsetsGeometry) EdgeInsetsDirectional.only(start: 16, end: 4), |
|||
height: material_._subHeaderHeight, |
|||
child: new Row( |
|||
children: rowChildren |
|||
) |
|||
); |
|||
} |
|||
|
|||
public override void dispose() { |
|||
_controller.dispose(); |
|||
base.dispose(); |
|||
} |
|||
} |
|||
|
|||
class _MonthPicker : StatefulWidget { |
|||
internal _MonthPicker( |
|||
Key key = null, |
|||
DateTime? initialMonth = null, |
|||
DateTime? currentDate = null, |
|||
DateTime? firstDate = null, |
|||
DateTime? lastDate = null, |
|||
DateTime? selectedDate = null, |
|||
ValueChanged<DateTime> onChanged = null, |
|||
ValueChanged<DateTime> onDisplayedMonthChanged = null, |
|||
material_.SelectableDayPredicate selectableDayPredicate = null |
|||
) : base(key: key) { |
|||
D.assert(selectedDate != null); |
|||
D.assert(currentDate != null); |
|||
D.assert(onChanged != null); |
|||
D.assert(firstDate != null); |
|||
D.assert(lastDate != null); |
|||
D.assert(!(firstDate > lastDate)); |
|||
D.assert(!(selectedDate < firstDate)); |
|||
D.assert(!(selectedDate > lastDate)); |
|||
this.initialMonth = initialMonth.Value; |
|||
this.currentDate = currentDate.Value; |
|||
this.firstDate = firstDate.Value; |
|||
this.lastDate = lastDate.Value; |
|||
this.selectedDate = selectedDate.Value; |
|||
this.onChanged = onChanged; |
|||
this.onDisplayedMonthChanged = onDisplayedMonthChanged; |
|||
this.selectableDayPredicate = selectableDayPredicate; |
|||
} |
|||
|
|||
public readonly DateTime initialMonth; |
|||
|
|||
public readonly DateTime currentDate; |
|||
|
|||
public readonly DateTime firstDate; |
|||
|
|||
public readonly DateTime lastDate; |
|||
|
|||
public readonly DateTime selectedDate; |
|||
|
|||
public readonly ValueChanged<DateTime> onChanged; |
|||
|
|||
public readonly ValueChanged<DateTime> onDisplayedMonthChanged; |
|||
|
|||
public readonly material_.SelectableDayPredicate selectableDayPredicate; |
|||
|
|||
public override State createState() => new _MonthPickerState(); |
|||
} |
|||
|
|||
class _MonthPickerState : State<_MonthPicker> { |
|||
DateTime _currentMonth; |
|||
DateTime _nextMonthDate; |
|||
DateTime _previousMonthDate; |
|||
PageController _pageController; |
|||
MaterialLocalizations _localizations; |
|||
TextDirection _textDirection; |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
_currentMonth = widget.initialMonth; |
|||
_previousMonthDate = utils.addMonthsToMonthDate(_currentMonth, -1); |
|||
_nextMonthDate = utils.addMonthsToMonthDate(_currentMonth, 1); |
|||
_pageController = new PageController(initialPage: utils.monthDelta(widget.firstDate, _currentMonth)); |
|||
} |
|||
|
|||
public override void didChangeDependencies() { |
|||
base.didChangeDependencies(); |
|||
_localizations = MaterialLocalizations.of(context); |
|||
_textDirection = Directionality.of(context); |
|||
} |
|||
|
|||
public override void dispose() { |
|||
_pageController?.dispose(); |
|||
base.dispose(); |
|||
} |
|||
|
|||
void _handleMonthPageChanged(int monthPage) { |
|||
DateTime monthDate = utils.addMonthsToMonthDate(widget.firstDate, monthPage); |
|||
if (_currentMonth.Year != monthDate.Year || _currentMonth.Month != monthDate.Month) { |
|||
_currentMonth = new DateTime(monthDate.Year, monthDate.Month, 1); |
|||
_previousMonthDate = utils.addMonthsToMonthDate(_currentMonth, -1); |
|||
_nextMonthDate = utils.addMonthsToMonthDate(_currentMonth, 1); |
|||
widget.onDisplayedMonthChanged?.Invoke(_currentMonth); |
|||
} |
|||
} |
|||
|
|||
void _handleNextMonth() { |
|||
if (!_isDisplayingLastMonth) { |
|||
// SemanticsService.announce(
|
|||
// _localizations.formatMonthYear(_nextMonthDate),
|
|||
// _textDirection,
|
|||
// );
|
|||
_pageController.nextPage( |
|||
duration: material_._monthScrollDuration, |
|||
curve: Curves.ease |
|||
); |
|||
} |
|||
} |
|||
|
|||
void _handlePreviousMonth() { |
|||
if (!_isDisplayingFirstMonth) { |
|||
// SemanticsService.announce(
|
|||
// _localizations.formatMonthYear(_previousMonthDate),
|
|||
// _textDirection,
|
|||
// );
|
|||
_pageController.previousPage( |
|||
duration: material_._monthScrollDuration, |
|||
curve: Curves.ease |
|||
); |
|||
} |
|||
} |
|||
|
|||
bool _isDisplayingFirstMonth { |
|||
get { return !(_currentMonth > new DateTime(widget.firstDate.Year, widget.firstDate.Month, 1)); } |
|||
} |
|||
|
|||
bool _isDisplayingLastMonth { |
|||
get { |
|||
return !(_currentMonth < new DateTime(widget.lastDate.Year, widget.lastDate.Month, 1) |
|||
); |
|||
} |
|||
} |
|||
|
|||
Widget _buildItems(BuildContext context, int index) { |
|||
DateTime month = utils.addMonthsToMonthDate(widget.firstDate, index); |
|||
return new _DayPicker( |
|||
key: new ValueKey<DateTime>(month), |
|||
selectedDate: widget.selectedDate, |
|||
currentDate: widget.currentDate, |
|||
onChanged: widget.onChanged, |
|||
firstDate: widget.firstDate, |
|||
lastDate: widget.lastDate, |
|||
displayedMonth: month, |
|||
selectableDayPredicate: widget.selectableDayPredicate |
|||
); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
string previousTooltipText = |
|||
$"{_localizations.previousMonthTooltip} {_localizations.formatMonthYear(_previousMonthDate)}"; |
|||
string nextTooltipText = |
|||
$"{_localizations.nextMonthTooltip} {_localizations.formatMonthYear(_nextMonthDate)}"; |
|||
Color controlColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.60f); |
|||
|
|||
return new Column( |
|||
children: new List<Widget> { |
|||
new Container( |
|||
//TODO: update EdgeInsetsGeometry
|
|||
padding: (EdgeInsets) (EdgeInsetsGeometry) EdgeInsetsDirectional.only(start: 16, end: 4), |
|||
height: material_._subHeaderHeight, |
|||
child: |
|||
new Row( |
|||
children: new List<Widget> { |
|||
new Spacer(), |
|||
new IconButton( |
|||
icon: new Icon(Icons.chevron_left), |
|||
color: controlColor, |
|||
tooltip: _isDisplayingFirstMonth ? null : previousTooltipText, |
|||
onPressed: _isDisplayingFirstMonth ? (VoidCallback) null : _handlePreviousMonth |
|||
), |
|||
new IconButton(icon: new Icon(Icons.chevron_right), |
|||
color: controlColor, |
|||
tooltip: _isDisplayingLastMonth ? null : nextTooltipText, |
|||
onPressed: _isDisplayingLastMonth ? (VoidCallback) null : _handleNextMonth |
|||
) |
|||
} |
|||
) |
|||
), |
|||
new _DayHeaders(), |
|||
new Expanded( |
|||
child: PageView.builder( |
|||
controller: _pageController, |
|||
itemBuilder: _buildItems, |
|||
itemCount: utils.monthDelta(widget.firstDate, widget.lastDate) + 1, |
|||
scrollDirection: Axis.horizontal, |
|||
onPageChanged: _handleMonthPageChanged |
|||
) |
|||
) |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _DayPicker : StatelessWidget { |
|||
internal _DayPicker( |
|||
Key key = null, |
|||
DateTime? currentDate = null, |
|||
DateTime? displayedMonth = null, |
|||
DateTime? firstDate = null, |
|||
DateTime? lastDate = null, |
|||
DateTime? selectedDate = null, |
|||
ValueChanged<DateTime> onChanged = null, |
|||
material_.SelectableDayPredicate selectableDayPredicate = null |
|||
) : base(key: key) { |
|||
D.assert(currentDate != null); |
|||
D.assert(displayedMonth != null); |
|||
D.assert(firstDate != null); |
|||
D.assert(lastDate != null); |
|||
D.assert(selectedDate != null); |
|||
D.assert(onChanged != null); |
|||
D.assert(!(firstDate > lastDate)); |
|||
D.assert(!(selectedDate < firstDate)); |
|||
D.assert(!(selectedDate > lastDate)); |
|||
this.currentDate = currentDate.Value; |
|||
this.displayedMonth = displayedMonth.Value; |
|||
this.firstDate = firstDate.Value; |
|||
this.lastDate = lastDate.Value; |
|||
this.selectedDate = selectedDate.Value; |
|||
this.onChanged = onChanged; |
|||
this.selectableDayPredicate = selectableDayPredicate; |
|||
} |
|||
|
|||
public readonly DateTime selectedDate; |
|||
|
|||
public readonly DateTime currentDate; |
|||
|
|||
public readonly ValueChanged<DateTime> onChanged; |
|||
|
|||
public readonly DateTime firstDate; |
|||
|
|||
public readonly DateTime lastDate; |
|||
|
|||
public readonly DateTime displayedMonth; |
|||
|
|||
public readonly material_.SelectableDayPredicate selectableDayPredicate; |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
ColorScheme colorScheme = Theme.of(context).colorScheme; |
|||
MaterialLocalizations localizations = MaterialLocalizations.of(context); |
|||
TextTheme textTheme = Theme.of(context).textTheme; |
|||
TextStyle dayStyle = textTheme.caption; |
|||
Color enabledDayColor = colorScheme.onSurface.withOpacity(0.87f); |
|||
Color disabledDayColor = colorScheme.onSurface.withOpacity(0.38f); |
|||
Color selectedDayColor = colorScheme.onPrimary; |
|||
Color selectedDayBackground = colorScheme.primary; |
|||
Color todayColor = colorScheme.primary; |
|||
|
|||
int year = displayedMonth.Year; |
|||
int month = displayedMonth.Month; |
|||
|
|||
int daysInMonth = utils.getDaysInMonth(year, month); |
|||
int dayOffset = utils.firstDayOffset(year, month, localizations); |
|||
|
|||
List<Widget> dayItems = new List<Widget>(); |
|||
// 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
|
|||
// a leap year.
|
|||
int day = -dayOffset; |
|||
while (day < daysInMonth) { |
|||
day++; |
|||
if (day < 1) { |
|||
dayItems.Add(new Container()); |
|||
} |
|||
else { |
|||
DateTime dayToBuild = new DateTime(year, month, day); |
|||
bool isDisabled = dayToBuild > lastDate || |
|||
dayToBuild < firstDate || |
|||
(selectableDayPredicate != null && !selectableDayPredicate(dayToBuild)); |
|||
|
|||
BoxDecoration decoration = null; |
|||
Color dayColor = enabledDayColor; |
|||
bool isSelectedDay = utils.isSameDay(selectedDate, dayToBuild); |
|||
if (isSelectedDay) { |
|||
// The selected day gets a circle background highlight, and a
|
|||
// contrasting text color.
|
|||
dayColor = selectedDayColor; |
|||
decoration = new BoxDecoration( |
|||
color: selectedDayBackground, |
|||
shape: BoxShape.circle |
|||
); |
|||
} |
|||
else if (isDisabled) { |
|||
dayColor = disabledDayColor; |
|||
} |
|||
else if (utils.isSameDay(currentDate, dayToBuild)) { |
|||
// The current day gets a different text color and a circle stroke
|
|||
// border.
|
|||
dayColor = todayColor; |
|||
decoration = new BoxDecoration( |
|||
border: Border.all(color: todayColor, width: 1), |
|||
shape: BoxShape.circle |
|||
); |
|||
} |
|||
|
|||
Widget dayWidget = new Container( |
|||
decoration: decoration, |
|||
child: new Center( |
|||
child: new Text(localizations.formatDecimal(day), style: dayStyle.apply(color: dayColor)) |
|||
) |
|||
); |
|||
|
|||
if (isDisabled) { |
|||
dayWidget = dayWidget; |
|||
} |
|||
else { |
|||
dayWidget = new GestureDetector( |
|||
behavior: HitTestBehavior.opaque, |
|||
onTap: () => onChanged(dayToBuild), |
|||
child: dayWidget |
|||
); |
|||
} |
|||
|
|||
dayItems.Add(dayWidget); |
|||
} |
|||
} |
|||
|
|||
return new Padding( |
|||
padding: EdgeInsets.symmetric( |
|||
horizontal: material_._monthPickerHorizontalPadding |
|||
), |
|||
child: GridView.custom( |
|||
physics: new ClampingScrollPhysics(), |
|||
gridDelegate: material_._dayPickerGridDelegate, |
|||
childrenDelegate: new SliverChildListDelegate( |
|||
dayItems, |
|||
addRepaintBoundaries: false |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _DayPickerGridDelegate : SliverGridDelegate { |
|||
internal _DayPickerGridDelegate() { |
|||
} |
|||
|
|||
const int daysPerWeek = 7; |
|||
|
|||
public override SliverGridLayout getLayout(SliverConstraints constraints) { |
|||
const int columnCount = daysPerWeek; |
|||
float tileWidth = constraints.crossAxisExtent / columnCount; |
|||
float tileHeight = Mathf.Min(material_._dayPickerRowHeight, |
|||
constraints.viewportMainAxisExtent / material_._maxDayPickerRowCount); |
|||
return new SliverGridRegularTileLayout( |
|||
childCrossAxisExtent: tileWidth, |
|||
childMainAxisExtent: tileHeight, |
|||
crossAxisCount: columnCount, |
|||
crossAxisStride: tileWidth, |
|||
mainAxisStride: tileHeight, |
|||
reverseCrossAxis: AxisUtils.axisDirectionIsReversed(constraints.crossAxisDirection) |
|||
); |
|||
} |
|||
|
|||
public override bool shouldRelayout(SliverGridDelegate sliverGridDelegate) => false; |
|||
} |
|||
|
|||
public partial class material_ { |
|||
internal static readonly _DayPickerGridDelegate _dayPickerGridDelegate = new _DayPickerGridDelegate(); |
|||
} |
|||
|
|||
class _DayHeaders : StatelessWidget { |
|||
List<Widget> _getDayHeaders(TextStyle headerStyle, MaterialLocalizations localizations) { |
|||
List<Widget> result = new List<Widget>(); |
|||
for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) { |
|||
string weekday = localizations.narrowWeekdays[i]; |
|||
result.Add(new Center(child: new Text(weekday, style: headerStyle))); |
|||
if (i == (localizations.firstDayOfWeekIndex - 1) % 7) |
|||
break; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
ThemeData theme = Theme.of(context); |
|||
ColorScheme colorScheme = theme.colorScheme; |
|||
TextStyle dayHeaderStyle = theme.textTheme.caption?.apply( |
|||
color: colorScheme.onSurface.withOpacity(0.60f) |
|||
); |
|||
MaterialLocalizations localizations = MaterialLocalizations.of(context); |
|||
List<Widget> |
|||
labels = _getDayHeaders(dayHeaderStyle, localizations); |
|||
|
|||
return new Padding( |
|||
padding: EdgeInsets.symmetric(horizontal: material_._monthPickerHorizontalPadding), |
|||
child: |
|||
GridView.custom( |
|||
shrinkWrap: true, |
|||
gridDelegate: material_._dayPickerGridDelegate, |
|||
childrenDelegate: new SliverChildListDelegate( |
|||
labels, |
|||
addRepaintBoundaries: false |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _YearPicker : StatefulWidget { |
|||
internal _YearPicker( |
|||
Key key = null, |
|||
DateTime? currentDate = null, |
|||
DateTime? firstDate = null, |
|||
DateTime? lastDate = null, |
|||
DateTime? initialDate = null, |
|||
DateTime? selectedDate = null, |
|||
ValueChanged<DateTime> onChanged = null |
|||
) : base(key: key) { |
|||
D.assert(currentDate != null); |
|||
D.assert(firstDate != null); |
|||
D.assert(lastDate != null); |
|||
D.assert(initialDate != null); |
|||
D.assert(selectedDate != null); |
|||
D.assert(onChanged != null); |
|||
D.assert(!(firstDate > lastDate)); |
|||
this.currentDate = currentDate.Value; |
|||
this.firstDate = firstDate.Value; |
|||
this.lastDate = lastDate.Value; |
|||
this.initialDate = initialDate.Value; |
|||
this.selectedDate = selectedDate.Value; |
|||
this.onChanged = onChanged; |
|||
} |
|||
|
|||
public readonly DateTime currentDate; |
|||
|
|||
public readonly DateTime firstDate; |
|||
|
|||
public readonly DateTime lastDate; |
|||
|
|||
public readonly DateTime initialDate; |
|||
|
|||
public readonly DateTime selectedDate; |
|||
|
|||
public readonly ValueChanged<DateTime> onChanged; |
|||
|
|||
public override |
|||
State createState() => new _YearPickerState(); |
|||
} |
|||
|
|||
class _YearPickerState : State<_YearPicker> { |
|||
ScrollController scrollController; |
|||
|
|||
// The approximate number of years necessary to fill the available space.
|
|||
public const int minYears = 18; |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
|
|||
// Set the scroll position to approximately center the initial year.
|
|||
int initialYearIndex = widget.selectedDate.Year - widget.firstDate.Year; |
|||
int initialYearRow = (int) (1.0f * initialYearIndex / material_._yearPickerColumnCount); |
|||
// Move the offset down by 2 rows to approximately center it.
|
|||
int centeredYearRow = initialYearRow - 2; |
|||
float scrollOffset = _itemCount < minYears ? 0 : centeredYearRow * material_._yearPickerRowHeight; |
|||
scrollController = new ScrollController(initialScrollOffset: scrollOffset); |
|||
} |
|||
|
|||
Widget _buildYearItem(BuildContext context, int index) { |
|||
ColorScheme colorScheme = Theme.of(context).colorScheme; |
|||
TextTheme textTheme = Theme.of(context).textTheme; |
|||
|
|||
// Backfill the _YearPicker with disabled years if necessary.
|
|||
int offset = _itemCount < minYears ? (int) (1.0f * (minYears - _itemCount) / 2) : 0; |
|||
int year = widget.firstDate.Year + index - offset; |
|||
bool isSelected = year == widget.selectedDate.Year; |
|||
bool isCurrentYear = year == widget.currentDate.Year; |
|||
bool isDisabled = year < widget.firstDate.Year || year > widget.lastDate.Year; |
|||
const float decorationHeight = 36.0f; |
|||
const float decorationWidth = 72.0f; |
|||
|
|||
Color textColor; |
|||
if (isSelected) { |
|||
textColor = colorScheme.onPrimary; |
|||
} |
|||
else if (isDisabled) { |
|||
textColor = colorScheme.onSurface.withOpacity(0.38f); |
|||
} |
|||
else if (isCurrentYear) { |
|||
textColor = colorScheme.primary; |
|||
} |
|||
else { |
|||
textColor = colorScheme.onSurface.withOpacity(0.87f); |
|||
} |
|||
|
|||
TextStyle itemStyle = textTheme.bodyText1?.apply(color: textColor); |
|||
|
|||
BoxDecoration decoration = null; |
|||
if (isSelected) { |
|||
decoration = new BoxDecoration( |
|||
color: colorScheme.primary, |
|||
borderRadius: BorderRadius.circular(decorationHeight / 2), |
|||
shape: BoxShape.rectangle |
|||
); |
|||
} |
|||
else if (isCurrentYear && !isDisabled) { |
|||
decoration = new BoxDecoration( |
|||
border: Border.all( |
|||
color: colorScheme.primary, |
|||
width: 1 |
|||
), |
|||
borderRadius: BorderRadius.circular(decorationHeight / 2), |
|||
shape: BoxShape.rectangle |
|||
); |
|||
} |
|||
|
|||
Widget yearItem = new Center( |
|||
child: new Container( |
|||
decoration: decoration, |
|||
height: decorationHeight, |
|||
width: decorationWidth, |
|||
child: new Center( |
|||
child: new Text(year.ToString(), style: itemStyle) |
|||
) |
|||
) |
|||
); |
|||
|
|||
if (isDisabled) { |
|||
yearItem = yearItem; |
|||
} |
|||
else { |
|||
yearItem = new InkWell( |
|||
key: new ValueKey<int>(year), |
|||
onTap: () => { |
|||
widget.onChanged( |
|||
new DateTime( |
|||
year, |
|||
widget.initialDate.Month, |
|||
widget.initialDate.Day |
|||
) |
|||
); |
|||
}, |
|||
child: yearItem |
|||
); |
|||
} |
|||
|
|||
return yearItem; |
|||
} |
|||
|
|||
int _itemCount { |
|||
get { return widget.lastDate.Year - widget.firstDate.Year + 1; } |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new Column( |
|||
children: new List<Widget> { |
|||
new Divider(), |
|||
new Expanded( |
|||
child: GridView.builder( |
|||
controller: scrollController, |
|||
gridDelegate: material_._yearPickerGridDelegate, |
|||
itemBuilder: _buildYearItem, |
|||
itemCount: Math.Max(_itemCount, minYears), |
|||
padding: EdgeInsets.symmetric(horizontal: material_._yearPickerPadding) |
|||
) |
|||
), |
|||
new Divider(), |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _YearPickerGridDelegate : SliverGridDelegate { |
|||
internal _YearPickerGridDelegate() { |
|||
} |
|||
|
|||
public override SliverGridLayout getLayout(SliverConstraints constraints) { |
|||
float tileWidth = |
|||
(constraints.crossAxisExtent - |
|||
(material_._yearPickerColumnCount - 1) * material_._yearPickerRowSpacing) / |
|||
material_._yearPickerColumnCount; |
|||
return new SliverGridRegularTileLayout( |
|||
childCrossAxisExtent: tileWidth, |
|||
childMainAxisExtent: material_._yearPickerRowHeight, |
|||
crossAxisCount: material_._yearPickerColumnCount, |
|||
crossAxisStride: tileWidth + material_._yearPickerRowSpacing, |
|||
mainAxisStride: material_._yearPickerRowHeight, |
|||
reverseCrossAxis: AxisUtils.axisDirectionIsReversed(constraints.crossAxisDirection) |
|||
); |
|||
} |
|||
|
|||
public override bool shouldRelayout(SliverGridDelegate sliverGridDelegate) => false; |
|||
} |
|||
|
|||
public partial class material_ { |
|||
internal static readonly _YearPickerGridDelegate _yearPickerGridDelegate = new _YearPickerGridDelegate(); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: b29a37617b764cbe8c7174802e4792f7 |
|||
timeCreated: 1611818184 |
|
|||
using System; |
|||
|
|||
namespace Unity.UIWidgets.material { |
|||
public enum DatePickerEntryMode { |
|||
calendar, |
|||
|
|||
input, |
|||
} |
|||
|
|||
public enum DatePickerMode { |
|||
day, |
|||
|
|||
year, |
|||
} |
|||
|
|||
public partial class material_ { |
|||
public delegate bool SelectableDayPredicate(DateTime day); |
|||
} |
|||
|
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: c80395d6c7ce4450a7c24ab750bbed67 |
|||
timeCreated: 1611820996 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Unity.UIWidgets.material { |
|||
public static partial class utils { |
|||
public static DateTime dateOnly(DateTime date) { |
|||
return new DateTime(date.Year, date.Month, date.Day); |
|||
} |
|||
|
|||
public static bool isSameDay(DateTime dateA, DateTime dateB) { |
|||
return |
|||
dateA.Year == dateB.Year && |
|||
dateA.Month == dateB.Month && |
|||
dateA.Day == dateB.Day; |
|||
} |
|||
|
|||
public static int monthDelta(DateTime startDate, DateTime endDate) { |
|||
return (endDate.Year - startDate.Year) * 12 + endDate.Month - startDate.Month; |
|||
} |
|||
|
|||
public static DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) { |
|||
return new DateTime(monthDate.Year, monthDate.Month + monthsToAdd, 1); |
|||
} |
|||
|
|||
public static int firstDayOffset(int year, int month, MaterialLocalizations localizations) { |
|||
// 0-based day of week for the month and year, with 0 representing Monday.
|
|||
int weekdayFromMonday = (int) ((new DateTime(year, month, 1)).DayOfWeek) - 1; |
|||
|
|||
// 0-based start of week depending on the locale, with 0 representing Sunday.
|
|||
int firstDayOfWeekIndex = localizations.firstDayOfWeekIndex; |
|||
|
|||
// firstDayOfWeekIndex recomputed to be Monday-based, in order to compare with
|
|||
// weekdayFromMonday.
|
|||
firstDayOfWeekIndex = (firstDayOfWeekIndex - 1) % 7; |
|||
|
|||
// Number of days between the first day of week appearing on the calendar,
|
|||
// and the day corresponding to the first of the month.
|
|||
return (weekdayFromMonday - firstDayOfWeekIndex) % 7; |
|||
} |
|||
|
|||
public const int february = 2; |
|||
|
|||
public static int getDaysInMonth(int year, int month) { |
|||
if (month == february) { |
|||
bool isLeapYear = (year % 4 == 0) && (year % 100 != 0) || |
|||
(year % 400 == 0); |
|||
if (isLeapYear) |
|||
return 29; |
|||
return 28; |
|||
} |
|||
|
|||
List<int> daysInMonth = new List<int> { |
|||
31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 |
|||
}; |
|||
return daysInMonth[month - 1]; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 5a5006599804476d959581f0fa09860f |
|||
timeCreated: 1611819837 |
撰写
预览
正在加载...
取消
保存
Reference in new issue