Improve date picker fidelity (#4023)
This patch updates the date picker to match the new spec. We're still missing a fade effect when scrolling from one month to another and we're missing the landscape layout. Fixes #3558
This commit is contained in:
parent
ef563c485e
commit
69311c28ba
@ -11,6 +11,8 @@ import 'package:intl/intl.dart';
|
|||||||
|
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
import 'debug.dart';
|
import 'debug.dart';
|
||||||
|
import 'icons.dart';
|
||||||
|
import 'icon_button.dart';
|
||||||
import 'ink_well.dart';
|
import 'ink_well.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
import 'typography.dart';
|
import 'typography.dart';
|
||||||
@ -88,8 +90,6 @@ class _DatePickerState extends State<DatePicker> {
|
|||||||
config.onChanged(dateTime);
|
config.onChanged(dateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const double _calendarHeight = _kMaxDayPickerHeight;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget header = new _DatePickerHeader(
|
Widget header = new _DatePickerHeader(
|
||||||
@ -104,8 +104,7 @@ class _DatePickerState extends State<DatePicker> {
|
|||||||
selectedDate: config.selectedDate,
|
selectedDate: config.selectedDate,
|
||||||
onChanged: _handleDayChanged,
|
onChanged: _handleDayChanged,
|
||||||
firstDate: config.firstDate,
|
firstDate: config.firstDate,
|
||||||
lastDate: config.lastDate,
|
lastDate: config.lastDate
|
||||||
itemExtent: _calendarHeight
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case _DatePickerMode.year:
|
case _DatePickerMode.year:
|
||||||
@ -122,7 +121,7 @@ class _DatePickerState extends State<DatePicker> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
header,
|
header,
|
||||||
new Container(
|
new Container(
|
||||||
height: _calendarHeight,
|
height: _kMaxDayPickerHeight,
|
||||||
child: picker
|
child: picker
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -154,11 +153,11 @@ class _DatePickerHeader extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ThemeData theme = Theme.of(context);
|
ThemeData themeData = Theme.of(context);
|
||||||
TextTheme headerTheme = theme.primaryTextTheme;
|
TextTheme headerTextTheme = themeData.primaryTextTheme;
|
||||||
Color dayColor;
|
Color dayColor;
|
||||||
Color yearColor;
|
Color yearColor;
|
||||||
switch(theme.primaryColorBrightness) {
|
switch(themeData.primaryColorBrightness) {
|
||||||
case ThemeBrightness.light:
|
case ThemeBrightness.light:
|
||||||
dayColor = mode == _DatePickerMode.day ? Colors.black87 : Colors.black54;
|
dayColor = mode == _DatePickerMode.day ? Colors.black87 : Colors.black54;
|
||||||
yearColor = mode == _DatePickerMode.year ? Colors.black87 : Colors.black54;
|
yearColor = mode == _DatePickerMode.year ? Colors.black87 : Colors.black54;
|
||||||
@ -168,34 +167,43 @@ class _DatePickerHeader extends StatelessWidget {
|
|||||||
yearColor = mode == _DatePickerMode.year ? Colors.white : Colors.white70;
|
yearColor = mode == _DatePickerMode.year ? Colors.white : Colors.white70;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
TextStyle dayStyle = headerTheme.display3.copyWith(color: dayColor, height: 1.0, fontSize: 100.0);
|
TextStyle dayStyle = headerTextTheme.display1.copyWith(color: dayColor, height: 1.4);
|
||||||
TextStyle monthStyle = headerTheme.headline.copyWith(color: dayColor, height: 1.0);
|
TextStyle yearStyle = headerTextTheme.subhead.copyWith(color: yearColor, height: 1.4);
|
||||||
TextStyle yearStyle = headerTheme.headline.copyWith(color: yearColor, height: 1.0);
|
|
||||||
|
Color backgroundColor;
|
||||||
|
switch (themeData.brightness) {
|
||||||
|
case ThemeBrightness.light:
|
||||||
|
backgroundColor = themeData.primaryColor;
|
||||||
|
break;
|
||||||
|
case ThemeBrightness.dark:
|
||||||
|
backgroundColor = themeData.backgroundColor;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return new Container(
|
return new Container(
|
||||||
padding: new EdgeInsets.all(10.0),
|
height: 100.0,
|
||||||
decoration: new BoxDecoration(backgroundColor: theme.primaryColor),
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
|
decoration: new BoxDecoration(backgroundColor: backgroundColor),
|
||||||
child: new Column(
|
child: new Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new GestureDetector(
|
|
||||||
onTap: () => _handleChangeMode(_DatePickerMode.day),
|
|
||||||
child: new Text(new DateFormat('MMM').format(selectedDate).toUpperCase(), style: monthStyle)
|
|
||||||
),
|
|
||||||
new GestureDetector(
|
|
||||||
onTap: () => _handleChangeMode(_DatePickerMode.day),
|
|
||||||
child: new Text(new DateFormat('d').format(selectedDate), style: dayStyle)
|
|
||||||
),
|
|
||||||
new GestureDetector(
|
new GestureDetector(
|
||||||
onTap: () => _handleChangeMode(_DatePickerMode.year),
|
onTap: () => _handleChangeMode(_DatePickerMode.year),
|
||||||
child: new Text(new DateFormat('yyyy').format(selectedDate), style: yearStyle)
|
child: new Text(new DateFormat('yyyy').format(selectedDate), style: yearStyle)
|
||||||
)
|
),
|
||||||
|
new GestureDetector(
|
||||||
|
onTap: () => _handleChangeMode(_DatePickerMode.day),
|
||||||
|
child: new Text(new DateFormat('MMMEd').format(selectedDate), style: dayStyle)
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const double _kDayPickerRowHeight = 30.0;
|
const Duration _kMonthScrollDuration = const Duration(milliseconds: 200);
|
||||||
|
const double _kDayPickerRowHeight = 42.0;
|
||||||
const int _kMaxDayPickerRowCount = 6; // A 31 day month that starts on Saturday.
|
const int _kMaxDayPickerRowCount = 6; // A 31 day month that starts on Saturday.
|
||||||
// Two extra rows: one for the day-of-week header and one for the month header.
|
// Two extra rows: one for the day-of-week header and one for the month header.
|
||||||
const double _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCount + 2);
|
const double _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCount + 2);
|
||||||
@ -269,10 +277,6 @@ class DayPicker extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ThemeData themeData = Theme.of(context);
|
final ThemeData themeData = Theme.of(context);
|
||||||
final TextStyle headerStyle = themeData.textTheme.caption.copyWith(fontWeight: FontWeight.w700);
|
|
||||||
final TextStyle monthStyle = headerStyle.copyWith(fontSize: 14.0, height: 24.0 / 14.0);
|
|
||||||
final TextStyle dayStyle = headerStyle.copyWith(fontWeight: FontWeight.w500);
|
|
||||||
|
|
||||||
final int year = displayedMonth.year;
|
final int year = displayedMonth.year;
|
||||||
final int month = displayedMonth.month;
|
final int month = displayedMonth.month;
|
||||||
// Dart's Date time constructor is very forgiving and will understand
|
// Dart's Date time constructor is very forgiving and will understand
|
||||||
@ -281,7 +285,7 @@ class DayPicker extends StatelessWidget {
|
|||||||
// This assumes a start day of SUNDAY, but could be changed.
|
// This assumes a start day of SUNDAY, but could be changed.
|
||||||
final int firstWeekday = new DateTime(year, month).weekday % 7;
|
final int firstWeekday = new DateTime(year, month).weekday % 7;
|
||||||
final List<Widget> labels = <Widget>[];
|
final List<Widget> labels = <Widget>[];
|
||||||
labels.addAll(_getDayHeaders(headerStyle));
|
labels.addAll(_getDayHeaders(themeData.textTheme.caption));
|
||||||
for (int i = 0; true; ++i) {
|
for (int i = 0; true; ++i) {
|
||||||
final int day = i - firstWeekday + 1;
|
final int day = i - firstWeekday + 1;
|
||||||
if (day > daysInMonth)
|
if (day > daysInMonth)
|
||||||
@ -290,13 +294,12 @@ class DayPicker extends StatelessWidget {
|
|||||||
labels.add(new Container());
|
labels.add(new Container());
|
||||||
} else {
|
} else {
|
||||||
BoxDecoration decoration;
|
BoxDecoration decoration;
|
||||||
TextStyle itemStyle = dayStyle;
|
TextStyle itemStyle = themeData.textTheme.body1;
|
||||||
|
|
||||||
if (selectedDate.year == year && selectedDate.month == month && selectedDate.day == day) {
|
if (selectedDate.year == year && selectedDate.month == month && selectedDate.day == day) {
|
||||||
// The selected day gets a circle background highlight, and a contrasting text color.
|
// The selected day gets a circle background highlight, and a contrasting text color.
|
||||||
final ThemeData theme = Theme.of(context);
|
itemStyle = themeData.textTheme.body2.copyWith(
|
||||||
itemStyle = itemStyle.copyWith(
|
color: (themeData.brightness == ThemeBrightness.light) ? Colors.white : Colors.black87
|
||||||
color: (theme.brightness == ThemeBrightness.light) ? Colors.white : Colors.black87
|
|
||||||
);
|
);
|
||||||
decoration = new BoxDecoration(
|
decoration = new BoxDecoration(
|
||||||
backgroundColor: themeData.accentColor,
|
backgroundColor: themeData.accentColor,
|
||||||
@ -304,7 +307,7 @@ class DayPicker extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
} else if (currentDate.year == year && currentDate.month == month && currentDate.day == day) {
|
} else if (currentDate.year == year && currentDate.month == month && currentDate.day == day) {
|
||||||
// The current day gets a different text color.
|
// The current day gets a different text color.
|
||||||
itemStyle = itemStyle.copyWith(color: themeData.accentColor);
|
itemStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
labels.add(new GestureDetector(
|
labels.add(new GestureDetector(
|
||||||
@ -323,15 +326,24 @@ class DayPicker extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Column(
|
return new Padding(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
children: <Widget>[
|
child: new Column(
|
||||||
new Text(new DateFormat('MMMM y').format(displayedMonth), style: monthStyle),
|
children: <Widget>[
|
||||||
new CustomGrid(
|
new Container(
|
||||||
delegate: _kDayPickerGridDelegate,
|
height: _kDayPickerRowHeight,
|
||||||
children: labels
|
child: new Center(
|
||||||
)
|
child: new Text(new DateFormat('yMMMM').format(displayedMonth),
|
||||||
]
|
style: themeData.textTheme.subhead
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new CustomGrid(
|
||||||
|
delegate: _kDayPickerGridDelegate,
|
||||||
|
children: labels
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -356,8 +368,7 @@ class MonthPicker extends StatefulWidget {
|
|||||||
this.selectedDate,
|
this.selectedDate,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.firstDate,
|
this.firstDate,
|
||||||
this.lastDate,
|
this.lastDate
|
||||||
this.itemExtent
|
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
assert(selectedDate != null);
|
assert(selectedDate != null);
|
||||||
assert(onChanged != null);
|
assert(onChanged != null);
|
||||||
@ -379,9 +390,6 @@ class MonthPicker extends StatefulWidget {
|
|||||||
/// The latest date the user is permitted to pick.
|
/// The latest date the user is permitted to pick.
|
||||||
final DateTime lastDate;
|
final DateTime lastDate;
|
||||||
|
|
||||||
/// The amount of vertical space to use for each month in the picker.
|
|
||||||
final double itemExtent;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MonthPickerState createState() => new _MonthPickerState();
|
_MonthPickerState createState() => new _MonthPickerState();
|
||||||
}
|
}
|
||||||
@ -393,8 +401,15 @@ class _MonthPickerState extends State<MonthPicker> {
|
|||||||
_updateCurrentDate();
|
_updateCurrentDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateConfig(MonthPicker oldConfig) {
|
||||||
|
if (config.selectedDate != oldConfig.selectedDate)
|
||||||
|
_dayPickerListKey = new GlobalKey<ScrollableState>();
|
||||||
|
}
|
||||||
|
|
||||||
DateTime _currentDate;
|
DateTime _currentDate;
|
||||||
Timer _timer;
|
Timer _timer;
|
||||||
|
GlobalKey<ScrollableState> _dayPickerListKey = new GlobalKey<ScrollableState>();
|
||||||
|
|
||||||
void _updateCurrentDate() {
|
void _updateCurrentDate() {
|
||||||
_currentDate = new DateTime.now();
|
_currentDate = new DateTime.now();
|
||||||
@ -420,7 +435,7 @@ class _MonthPickerState extends State<MonthPicker> {
|
|||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
DateTime displayedMonth = new DateTime(startDate.year + i ~/ 12, startDate.month + i % 12);
|
DateTime displayedMonth = new DateTime(startDate.year + i ~/ 12, startDate.month + i % 12);
|
||||||
result.add(new DayPicker(
|
result.add(new DayPicker(
|
||||||
key: new ObjectKey(displayedMonth),
|
key: new ValueKey<DateTime>(displayedMonth),
|
||||||
selectedDate: config.selectedDate,
|
selectedDate: config.selectedDate,
|
||||||
currentDate: _currentDate,
|
currentDate: _currentDate,
|
||||||
onChanged: config.onChanged,
|
onChanged: config.onChanged,
|
||||||
@ -430,14 +445,46 @@ class _MonthPickerState extends State<MonthPicker> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleNextMonth() {
|
||||||
|
ScrollableState state = _dayPickerListKey.currentState;
|
||||||
|
state?.scrollTo(state.scrollOffset.round() + 1.0, duration: _kMonthScrollDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handlePreviousMonth() {
|
||||||
|
ScrollableState state = _dayPickerListKey.currentState;
|
||||||
|
state?.scrollTo(state.scrollOffset.round() - 1.0, duration: _kMonthScrollDuration);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new ScrollableLazyList(
|
return new Stack(
|
||||||
key: new ValueKey<DateTime>(config.selectedDate),
|
children: <Widget>[
|
||||||
initialScrollOffset: config.itemExtent * _monthDelta(config.firstDate, config.selectedDate),
|
new PageableLazyList(
|
||||||
itemExtent: config.itemExtent,
|
key: _dayPickerListKey,
|
||||||
itemCount: _monthDelta(config.firstDate, config.lastDate) + 1,
|
initialScrollOffset: _monthDelta(config.firstDate, config.selectedDate).toDouble(),
|
||||||
itemBuilder: _buildItems
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: _monthDelta(config.firstDate, config.lastDate) + 1,
|
||||||
|
itemBuilder: _buildItems
|
||||||
|
),
|
||||||
|
new Positioned(
|
||||||
|
top: 0.0,
|
||||||
|
left: 8.0,
|
||||||
|
child: new IconButton(
|
||||||
|
icon: Icons.chevron_left,
|
||||||
|
tooltip: 'Previous month',
|
||||||
|
onPressed: _handlePreviousMonth
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new Positioned(
|
||||||
|
top: 0.0,
|
||||||
|
right: 8.0,
|
||||||
|
child: new IconButton(
|
||||||
|
icon: Icons.chevron_right,
|
||||||
|
tooltip: 'Next month',
|
||||||
|
onPressed: _handleNextMonth
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,29 +547,22 @@ class _YearPickerState extends State<YearPicker> {
|
|||||||
static const double _itemExtent = 50.0;
|
static const double _itemExtent = 50.0;
|
||||||
|
|
||||||
List<Widget> _buildItems(BuildContext context, int start, int count) {
|
List<Widget> _buildItems(BuildContext context, int start, int count) {
|
||||||
TextStyle style = Theme.of(context).textTheme.body1.copyWith(color: Colors.black54);
|
final ThemeData themeData = Theme.of(context);
|
||||||
List<Widget> items = new List<Widget>();
|
final TextStyle style = themeData.textTheme.body1;
|
||||||
|
final List<Widget> items = new List<Widget>();
|
||||||
for (int i = start; i < start + count; i++) {
|
for (int i = start; i < start + count; i++) {
|
||||||
int year = config.firstDate.year + i;
|
final int year = config.firstDate.year + i;
|
||||||
String label = year.toString();
|
final TextStyle itemStyle = year == config.selectedDate.year ?
|
||||||
Widget item = new InkWell(
|
themeData.textTheme.headline.copyWith(color: themeData.accentColor) : style;
|
||||||
key: new Key(label),
|
items.add(new InkWell(
|
||||||
|
key: new ValueKey<int>(year),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
DateTime result = new DateTime(year, config.selectedDate.month, config.selectedDate.day);
|
config.onChanged(new DateTime(year, config.selectedDate.month, config.selectedDate.day));
|
||||||
config.onChanged(result);
|
|
||||||
},
|
},
|
||||||
child: new Container(
|
child: new Center(
|
||||||
height: _itemExtent,
|
child: new Text(year.toString(), style: itemStyle)
|
||||||
decoration: year == config.selectedDate.year ? new BoxDecoration(
|
|
||||||
backgroundColor: Theme.of(context).backgroundColor,
|
|
||||||
shape: BoxShape.circle
|
|
||||||
) : null,
|
|
||||||
child: new Center(
|
|
||||||
child: new Text(label, style: style)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
));
|
||||||
items.add(item);
|
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,8 @@ enum PageableListFlingBehavior {
|
|||||||
stopAtNextPage
|
stopAtNextPage
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scrollable widget that scrolls one "page" at a time.
|
abstract class PageableListBase extends Scrollable {
|
||||||
///
|
PageableListBase({
|
||||||
/// In a pageable list, one child is visible at a time. Scrolling the list
|
|
||||||
/// reveals either the next or previous child.
|
|
||||||
class PageableList extends Scrollable {
|
|
||||||
PageableList({
|
|
||||||
Key key,
|
Key key,
|
||||||
double initialScrollOffset,
|
double initialScrollOffset,
|
||||||
Axis scrollDirection: Axis.vertical,
|
Axis scrollDirection: Axis.vertical,
|
||||||
@ -36,8 +32,7 @@ class PageableList extends Scrollable {
|
|||||||
this.itemsSnapAlignment: PageableListFlingBehavior.stopAtNextPage,
|
this.itemsSnapAlignment: PageableListFlingBehavior.stopAtNextPage,
|
||||||
this.onPageChanged,
|
this.onPageChanged,
|
||||||
this.duration: const Duration(milliseconds: 200),
|
this.duration: const Duration(milliseconds: 200),
|
||||||
this.curve: Curves.ease,
|
this.curve: Curves.ease
|
||||||
this.children
|
|
||||||
}) : super(
|
}) : super(
|
||||||
key: key,
|
key: key,
|
||||||
initialScrollOffset: initialScrollOffset,
|
initialScrollOffset: initialScrollOffset,
|
||||||
@ -66,19 +61,102 @@ class PageableList extends Scrollable {
|
|||||||
/// The animation curve to use when animating to a given page.
|
/// The animation curve to use when animating to a given page.
|
||||||
final Curve curve;
|
final Curve curve;
|
||||||
|
|
||||||
|
int get _itemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scrollable widget that scrolls one "page" at a time.
|
||||||
|
///
|
||||||
|
/// In a pageable list, one child is visible at a time. Scrolling the list
|
||||||
|
/// reveals either the next or previous child.
|
||||||
|
class PageableList extends PageableListBase {
|
||||||
|
PageableList({
|
||||||
|
Key key,
|
||||||
|
double initialScrollOffset,
|
||||||
|
Axis scrollDirection: Axis.vertical,
|
||||||
|
ViewportAnchor scrollAnchor: ViewportAnchor.start,
|
||||||
|
ScrollListener onScrollStart,
|
||||||
|
ScrollListener onScroll,
|
||||||
|
ScrollListener onScrollEnd,
|
||||||
|
SnapOffsetCallback snapOffsetCallback,
|
||||||
|
bool itemsWrap: false,
|
||||||
|
PageableListFlingBehavior itemsSnapAlignment: PageableListFlingBehavior.stopAtNextPage,
|
||||||
|
ValueChanged<int> onPageChanged,
|
||||||
|
Duration duration: const Duration(milliseconds: 200),
|
||||||
|
Curve curve: Curves.ease,
|
||||||
|
this.children
|
||||||
|
}) : super(
|
||||||
|
key: key,
|
||||||
|
initialScrollOffset: initialScrollOffset,
|
||||||
|
scrollDirection: scrollDirection,
|
||||||
|
scrollAnchor: scrollAnchor,
|
||||||
|
onScrollStart: onScrollStart,
|
||||||
|
onScroll: onScroll,
|
||||||
|
onScrollEnd: onScrollEnd,
|
||||||
|
snapOffsetCallback: snapOffsetCallback,
|
||||||
|
itemsWrap: itemsWrap,
|
||||||
|
itemsSnapAlignment: itemsSnapAlignment,
|
||||||
|
onPageChanged: onPageChanged,
|
||||||
|
duration: duration,
|
||||||
|
curve: curve
|
||||||
|
);
|
||||||
|
|
||||||
/// The list of pages themselves.
|
/// The list of pages themselves.
|
||||||
final Iterable<Widget> children;
|
final Iterable<Widget> children;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get _itemCount => children?.length ?? 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PageableListState<PageableList> createState() => new PageableListState<PageableList>();
|
PageableListState<PageableList> createState() => new PageableListState<PageableList>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State for a [PageableList] widget.
|
class PageableLazyList extends PageableListBase {
|
||||||
///
|
PageableLazyList({
|
||||||
/// Widgets that subclass [PageableList] can subclass this class to have
|
Key key,
|
||||||
/// sensible default behaviors for pageable lists.
|
double initialScrollOffset,
|
||||||
class PageableListState<T extends PageableList> extends ScrollableState<T> {
|
Axis scrollDirection: Axis.vertical,
|
||||||
int get _itemCount => config.children?.length ?? 0;
|
ViewportAnchor scrollAnchor: ViewportAnchor.start,
|
||||||
|
ScrollListener onScrollStart,
|
||||||
|
ScrollListener onScroll,
|
||||||
|
ScrollListener onScrollEnd,
|
||||||
|
SnapOffsetCallback snapOffsetCallback,
|
||||||
|
PageableListFlingBehavior itemsSnapAlignment: PageableListFlingBehavior.stopAtNextPage,
|
||||||
|
ValueChanged<int> onPageChanged,
|
||||||
|
Duration duration: const Duration(milliseconds: 200),
|
||||||
|
Curve curve: Curves.ease,
|
||||||
|
this.itemCount,
|
||||||
|
this.itemBuilder
|
||||||
|
}) : super(
|
||||||
|
key: key,
|
||||||
|
initialScrollOffset: initialScrollOffset,
|
||||||
|
scrollDirection: scrollDirection,
|
||||||
|
scrollAnchor: scrollAnchor,
|
||||||
|
onScrollStart: onScrollStart,
|
||||||
|
onScroll: onScroll,
|
||||||
|
onScrollEnd: onScrollEnd,
|
||||||
|
snapOffsetCallback: snapOffsetCallback,
|
||||||
|
itemsWrap: false,
|
||||||
|
itemsSnapAlignment: itemsSnapAlignment,
|
||||||
|
onPageChanged: onPageChanged,
|
||||||
|
duration: duration,
|
||||||
|
curve: curve
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The total number of list items.
|
||||||
|
final int itemCount;
|
||||||
|
|
||||||
|
/// A function that returns the pages themselves.
|
||||||
|
final ItemListBuilder itemBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get _itemCount => itemCount ?? 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PageableLazyListState createState() => new _PageableLazyListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _PageableListStateBase<T extends PageableListBase> extends ScrollableState<T> {
|
||||||
|
int get _itemCount => config._itemCount;
|
||||||
int _previousItemCount;
|
int _previousItemCount;
|
||||||
|
|
||||||
double get _pixelsPerScrollUnit {
|
double get _pixelsPerScrollUnit {
|
||||||
@ -124,7 +202,7 @@ class PageableListState<T extends PageableList> extends ScrollableState<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateConfig(PageableList oldConfig) {
|
void didUpdateConfig(PageableListBase oldConfig) {
|
||||||
super.didUpdateConfig(oldConfig);
|
super.didUpdateConfig(oldConfig);
|
||||||
|
|
||||||
bool scrollBehaviorUpdateNeeded = config.scrollDirection != oldConfig.scrollDirection;
|
bool scrollBehaviorUpdateNeeded = config.scrollDirection != oldConfig.scrollDirection;
|
||||||
@ -149,17 +227,6 @@ class PageableListState<T extends PageableList> extends ScrollableState<T> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildContent(BuildContext context) {
|
|
||||||
return new PageViewport(
|
|
||||||
itemsWrap: config.itemsWrap,
|
|
||||||
mainAxis: config.scrollDirection,
|
|
||||||
anchor: config.scrollAnchor,
|
|
||||||
startOffset: scrollOffset,
|
|
||||||
children: config.children
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
UnboundedBehavior _unboundedBehavior;
|
UnboundedBehavior _unboundedBehavior;
|
||||||
OverscrollBehavior _overscrollBehavior;
|
OverscrollBehavior _overscrollBehavior;
|
||||||
|
|
||||||
@ -216,15 +283,44 @@ class PageableListState<T extends PageableList> extends ScrollableState<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PageViewport extends VirtualViewportFromIterable {
|
/// State for a [PageableList] widget.
|
||||||
PageViewport({
|
///
|
||||||
this.startOffset: 0.0,
|
/// Widgets that subclass [PageableList] can subclass this class to have
|
||||||
this.mainAxis: Axis.vertical,
|
/// sensible default behaviors for pageable lists.
|
||||||
this.anchor: ViewportAnchor.start,
|
class PageableListState<T extends PageableList> extends _PageableListStateBase<T> {
|
||||||
this.itemsWrap: false,
|
@override
|
||||||
this.overlayPainter,
|
Widget buildContent(BuildContext context) {
|
||||||
this.children
|
return new PageViewport(
|
||||||
}) {
|
itemsWrap: config.itemsWrap,
|
||||||
|
mainAxis: config.scrollDirection,
|
||||||
|
anchor: config.scrollAnchor,
|
||||||
|
startOffset: scrollOffset,
|
||||||
|
children: config.children
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PageableLazyListState extends _PageableListStateBase<PageableLazyList> {
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context) {
|
||||||
|
return new LazyPageViewport(
|
||||||
|
mainAxis: config.scrollDirection,
|
||||||
|
anchor: config.scrollAnchor,
|
||||||
|
startOffset: scrollOffset,
|
||||||
|
itemCount: config.itemCount,
|
||||||
|
itemBuilder: config.itemBuilder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VirtualPageViewport extends VirtualViewport {
|
||||||
|
_VirtualPageViewport(
|
||||||
|
this.startOffset,
|
||||||
|
this.mainAxis,
|
||||||
|
this.anchor,
|
||||||
|
this.itemsWrap,
|
||||||
|
this.overlayPainter
|
||||||
|
) {
|
||||||
assert(mainAxis != null);
|
assert(mainAxis != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,21 +332,18 @@ class PageViewport extends VirtualViewportFromIterable {
|
|||||||
final bool itemsWrap;
|
final bool itemsWrap;
|
||||||
final RenderObjectPainter overlayPainter;
|
final RenderObjectPainter overlayPainter;
|
||||||
|
|
||||||
@override
|
|
||||||
final Iterable<Widget> children;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderList createRenderObject(BuildContext context) => new RenderList();
|
RenderList createRenderObject(BuildContext context) => new RenderList();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_PageViewportElement createElement() => new _PageViewportElement(this);
|
_VirtualPageViewportElement createElement() => new _VirtualPageViewportElement(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PageViewportElement extends VirtualViewportElement {
|
class _VirtualPageViewportElement extends VirtualViewportElement {
|
||||||
_PageViewportElement(PageViewport widget) : super(widget);
|
_VirtualPageViewportElement(_VirtualPageViewport widget) : super(widget);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PageViewport get widget => super.widget;
|
_VirtualPageViewport get widget => super.widget;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderList get renderObject => super.renderObject;
|
RenderList get renderObject => super.renderObject;
|
||||||
@ -279,7 +372,7 @@ class _PageViewportElement extends VirtualViewportElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateRenderObject(PageViewport oldWidget) {
|
void updateRenderObject(_VirtualPageViewport oldWidget) {
|
||||||
renderObject
|
renderObject
|
||||||
..mainAxis = widget.mainAxis
|
..mainAxis = widget.mainAxis
|
||||||
..overlayPainter = widget.overlayPainter;
|
..overlayPainter = widget.overlayPainter;
|
||||||
@ -342,3 +435,46 @@ class _PageViewportElement extends VirtualViewportElement {
|
|||||||
super.layout(constraints);
|
super.layout(constraints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PageViewport extends _VirtualPageViewport with VirtualViewportFromIterable {
|
||||||
|
PageViewport({
|
||||||
|
double startOffset: 0.0,
|
||||||
|
Axis mainAxis: Axis.vertical,
|
||||||
|
ViewportAnchor anchor: ViewportAnchor.start,
|
||||||
|
bool itemsWrap: false,
|
||||||
|
RenderObjectPainter overlayPainter,
|
||||||
|
this.children
|
||||||
|
}) : super(
|
||||||
|
startOffset,
|
||||||
|
mainAxis,
|
||||||
|
anchor,
|
||||||
|
itemsWrap,
|
||||||
|
overlayPainter
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final Iterable<Widget> children;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LazyPageViewport extends _VirtualPageViewport with VirtualViewportFromBuilder {
|
||||||
|
LazyPageViewport({
|
||||||
|
double startOffset: 0.0,
|
||||||
|
Axis mainAxis: Axis.vertical,
|
||||||
|
ViewportAnchor anchor: ViewportAnchor.start,
|
||||||
|
RenderObjectPainter overlayPainter,
|
||||||
|
this.itemCount,
|
||||||
|
this.itemBuilder
|
||||||
|
}) : super(
|
||||||
|
startOffset,
|
||||||
|
mainAxis,
|
||||||
|
anchor,
|
||||||
|
false, // Don't support wrapping yet.
|
||||||
|
overlayPainter
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int itemCount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final ItemListBuilder itemBuilder;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user