Commit 2eb4f2c3 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add PageController (#7948)

This patch improves PageView to the point where we can use it in the date
picker. Specifically, you now get onPageChanged notifications and you can
control which page is visible using a PageController.
parent 6ddd0bb4
......@@ -365,23 +365,24 @@ class _MonthPickerState extends State<MonthPicker> {
void initState() {
super.initState();
// Initially display the pre-selected date.
_dayPickerController = new PageController(initialPage: _monthDelta(config.firstDate, config.selectedDate).toDouble());
_currentDisplayedMonthDate = new DateTime(config.selectedDate.year, config.selectedDate.month);
_updateCurrentDate();
}
@override
void didUpdateConfig(MonthPicker oldConfig) {
if (config.selectedDate != oldConfig.selectedDate)
_dayPickerListKey = new GlobalKey<PageableState<PageableLazyList>>();
if (config.selectedDate != oldConfig.selectedDate) {
_dayPickerController = new PageController(initialPage: _monthDelta(config.firstDate, config.selectedDate).toDouble());
_currentDisplayedMonthDate =
new DateTime(config.selectedDate.year, config.selectedDate.month);
}
}
DateTime _todayDate;
DateTime _currentDisplayedMonthDate;
Timer _timer;
GlobalKey<PageableState<PageableLazyList>> _dayPickerListKey =
new GlobalKey<PageableState<PageableLazyList>>();
PageController _dayPickerController;
void _updateCurrentDate() {
_todayDate = new DateTime.now();
......@@ -397,7 +398,7 @@ class _MonthPickerState extends State<MonthPicker> {
});
}
int _monthDelta(DateTime startDate, DateTime endDate) {
static int _monthDelta(DateTime startDate, DateTime endDate) {
return (endDate.year - startDate.year) * 12 + endDate.month - startDate.month;
}
......@@ -406,35 +407,28 @@ class _MonthPickerState extends State<MonthPicker> {
return new DateTime(monthDate.year + monthsToAdd ~/ 12, monthDate.month + monthsToAdd % 12);
}
List<Widget> _buildItems(BuildContext context, int start, int count) {
final List<Widget> result = new List<Widget>();
final DateTime startMonthDate = _addMonthsToMonthDate(config.firstDate, start);
for (int i = 0; i < count; ++i) {
DateTime monthToBuild = _addMonthsToMonthDate(startMonthDate, i);
result.add(new DayPicker(
key: new ValueKey<DateTime>(monthToBuild),
selectedDate: config.selectedDate,
currentDate: _todayDate,
onChanged: config.onChanged,
firstDate: config.firstDate,
lastDate: config.lastDate,
displayedMonth: monthToBuild,
selectableDayPredicate: config.selectableDayPredicate,
));
}
return result;
Widget _buildItems(BuildContext context, int index) {
final DateTime month = _addMonthsToMonthDate(config.firstDate, index);
return new DayPicker(
key: new ValueKey<DateTime>(month),
selectedDate: config.selectedDate,
currentDate: _todayDate,
onChanged: config.onChanged,
firstDate: config.firstDate,
lastDate: config.lastDate,
displayedMonth: month,
selectableDayPredicate: config.selectableDayPredicate,
);
}
void _handleNextMonth() {
if (!_isDisplayingLastMonth) {
_dayPickerListKey.currentState?.fling(1.0);
}
if (!_isDisplayingLastMonth)
_dayPickerController.nextPage(duration: _kMonthScrollDuration, curve: Curves.ease);
}
void _handlePreviousMonth() {
if (!_isDisplayingFirstMonth) {
_dayPickerListKey.currentState?.fling(-1.0);
}
if (!_isDisplayingFirstMonth)
_dayPickerController.previousPage(duration: _kMonthScrollDuration, curve: Curves.ease);
}
/// True if the earliest allowable month is displayed.
......@@ -462,13 +456,12 @@ class _MonthPickerState extends State<MonthPicker> {
height: _kMaxDayPickerHeight,
child: new Stack(
children: <Widget>[
new PageableLazyList(
key: _dayPickerListKey,
initialScrollOffset: _monthDelta(config.firstDate, config.selectedDate).toDouble(),
new PageView.builder(
key: new ValueKey<DateTime>(config.selectedDate),
controller: _dayPickerController,
scrollDirection: Axis.horizontal,
itemCount: _monthDelta(config.firstDate, config.lastDate) + 1,
itemBuilder: _buildItems,
duration: _kMonthScrollDuration,
onPageChanged: _handleMonthPageChanged,
),
new Positioned(
......
......@@ -1608,6 +1608,8 @@ class RenderViewport2 extends RenderViewportBase2<SliverPhysicalContainerParentD
void performResize() {
assert(constraints.hasBoundedHeight && constraints.hasBoundedWidth);
size = constraints.biggest;
// We ignore the return value of applyViewportDimension below because we are
// going to go through performLayout next regardless.
switch (axis) {
case Axis.vertical:
offset.applyViewportDimension(size.height);
......@@ -1835,12 +1837,14 @@ class RenderViewport2 extends RenderViewportBase2<SliverPhysicalContainerParentD
break;
}
assert(effectiveExtent != null);
offset.applyViewportDimension(effectiveExtent);
return offset.applyContentDimensions(
// when updating this, also update similar code in performLayout()
math.min(0.0, _minScrollExtent + effectiveExtent * anchor),
math.max(0.0, _maxScrollExtent - effectiveExtent * (1.0 - anchor)),
);
final bool didAcceptViewportDimension = offset.applyViewportDimension(effectiveExtent);
final bool didAcceptContentDimension = offset.applyContentDimensions(
// when updating this, also update similar code in performLayout() and
// performResize().
math.min(0.0, _minScrollExtent + effectiveExtent * anchor),
math.max(0.0, _maxScrollExtent - effectiveExtent * (1.0 - anchor)),
);
return didAcceptViewportDimension && didAcceptContentDimension;
}
@override
......@@ -1990,9 +1994,10 @@ class RenderShrinkWrappingViewport extends RenderViewportBase2<SliverLogicalCont
effectiveExtent = constraints.constrainWidth(_shrinkWrapExtent);
break;
}
offset.applyViewportDimension(effectiveExtent);
// when updating this, also update similar code in rereportDimensions
if (offset.applyContentDimensions(0.0, math.max(0.0, _maxScrollExtent - effectiveExtent)))
final bool didAcceptViewportDimension = offset.applyViewportDimension(effectiveExtent);
final bool didAcceptContentDimension = offset.applyContentDimensions(0.0, math.max(0.0, _maxScrollExtent - effectiveExtent));
if (didAcceptViewportDimension && didAcceptContentDimension)
break;
}
} while (true);
......@@ -2103,8 +2108,9 @@ class RenderShrinkWrappingViewport extends RenderViewportBase2<SliverLogicalCont
break;
}
assert(effectiveExtent != null);
offset.applyViewportDimension(effectiveExtent);
return offset.applyContentDimensions(0.0, math.max(0.0, _maxScrollExtent - effectiveExtent));
final bool didAcceptViewportDimension = offset.applyViewportDimension(effectiveExtent);
final bool didAcceptContentDimension = offset.applyContentDimensions(0.0, math.max(0.0, _maxScrollExtent - effectiveExtent));
return didAcceptViewportDimension && didAcceptContentDimension;
}
@override
......
......@@ -66,10 +66,10 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
trailingChildWithLayout ??= child;
}
assert(childScrollOffset(firstChild) <= scrollOffset);
if (trailingChildWithLayout == null) {
firstChild.layout(childConstraints);
final SliverMultiBoxAdaptorParentData childParentData = firstChild.parentData;
childParentData.layoutOffset = indexToScrollOffset(firstIndex);
trailingChildWithLayout = firstChild;
}
......@@ -94,6 +94,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
final double leadingScrollOffset = indexToScrollOffset(firstIndex);
final double trailingScrollOffset = indexToScrollOffset(lastIndex + 1);
assert(childScrollOffset(firstChild) <= scrollOffset);
assert(debugAssertChildListIsNonEmptyAndContiguous());
assert(indexOf(firstChild) == firstIndex);
assert(lastIndex <= targetLastIndex);
......
......@@ -492,14 +492,11 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
trailingScrollOffset = math.max(trailingScrollOffset, gridGeometry.trailingScrollOffset);
}
assert(childScrollOffset(firstChild) <= scrollOffset);
if (trailingChildWithLayout == null) {
firstChild.layout(firstChildGridGeometry.getBoxConstraints(constraints));
final SliverGridParentData childParentData = firstChild.parentData;
childParentData.layoutOffset = firstChildGridGeometry.scrollOffset;
childParentData.crossAxisOffset = firstChildGridGeometry.crossAxisOffset;
assert(childParentData.layoutOffset ==
firstChildGridGeometry.scrollOffset);
trailingChildWithLayout = firstChild;
}
......@@ -527,6 +524,7 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
final int lastIndex = indexOf(lastChild);
assert(childScrollOffset(firstChild) <= scrollOffset);
assert(debugAssertChildListIsNonEmptyAndContiguous());
assert(indexOf(firstChild) == firstIndex);
assert(lastIndex <= targetLastIndex);
......
......@@ -61,7 +61,15 @@ abstract class ViewportOffset extends ChangeNotifier {
/// same layout phase. If the viewport is not configured to shrink-wrap its
/// contents, then this will only be called when the viewport recomputes its
/// size (i.e. when its parent lays out), and not during normal scrolling.
void applyViewportDimension(double viewportDimension);
///
/// If applying the viewport dimentions changes the scroll offset, return
/// false. Otherwise, return true. If you return false, the [RenderViewport2]
/// will be laid out again with the new scroll offset. This is expensive. (The
/// return value is answering the question "did you accept these viewport
/// dimensions unconditionally?"; if the new dimensions change the
/// [ViewportOffset]'s actual [pixels] value, then the viewport will need to
/// be laid out again.)
bool applyViewportDimension(double viewportDimension);
/// Called when the viewport's content extents are established.
///
......@@ -130,7 +138,7 @@ class _FixedViewportOffset extends ViewportOffset {
double get pixels => _pixels;
@override
void applyViewportDimension(double viewportDimension) { }
bool applyViewportDimension(double viewportDimension) => true;
@override
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) => true;
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:meta/meta.dart';
import 'basic.dart';
import 'framework.dart';
import 'notification_listener.dart';
import 'scroll_controller.dart';
import 'scroll_notification.dart';
import 'scroll_physics.dart';
import 'scroll_position.dart';
import 'scroll_view.dart';
import 'sliver.dart';
class PageController extends ScrollController {
PageController({
this.initialPage: 0.0,
});
final double initialPage;
double get page {
final ScrollPosition position = this.position;
return position.pixels / position.viewportDimension;
}
Future<Null> animateToPage(int page, {
@required Duration duration,
@required Curve curve,
}) {
final ScrollPosition position = this.position;
return position.animateTo(page * position.viewportDimension, duration: duration, curve: curve);
}
void jumpToPage(double page) {
final ScrollPosition position = this.position;
position.jumpTo(page * position.viewportDimension);
}
void nextPage({ @required Duration duration, @required Curve curve }) {
animateToPage(page.round() + 1, duration: duration, curve: curve);
}
void previousPage({ @required Duration duration, @required Curve curve }) {
animateToPage(page.round() - 1, duration: duration, curve: curve);
}
@override
ScrollPosition createScrollPosition(ScrollPhysics physics, AbstractScrollState state, ScrollPosition oldPosition) {
return new _PagePosition(
physics: physics,
state: state,
initialPage: initialPage,
oldPosition: oldPosition,
);
}
}
class _PagePosition extends ScrollPosition {
_PagePosition({
ScrollPhysics physics,
AbstractScrollState state,
this.initialPage: 0.0,
ScrollPosition oldPosition,
}) : super(
physics: physics,
state: state,
initialPixels: null,
oldPosition: oldPosition,
);
final double initialPage;
@override
bool applyViewportDimension(double viewportDimension) {
final double oldViewportDimensions = this.viewportDimension;
final bool result = super.applyViewportDimension(viewportDimension);
final double oldPixels = pixels;
final double page = oldPixels == null ? initialPage : oldPixels / oldViewportDimensions;
final double newPixels = page * viewportDimension;
if (newPixels != oldPixels) {
correctPixels(newPixels);
return false;
}
return result;
}
}
// Having this global (mutable) page controller is a bit of a hack. We need it
// to plumb in the factory for _PagePosition, but it will end up accumulating
// a large list of scroll positions. As long as you don't try to actually
// control the scroll positions, everything should be fine.
final PageController _defaultPageController = new PageController();
/// A scrollable list that works page by page.
// TODO(ianh): More documentation here.
///
/// See also:
///
/// * [SingleChildScrollView], when you need to make a single child scrollable.
/// * [ListView], for a scrollable list of boxes.
/// * [GridView], for a scrollable grid of boxes.
class PageView extends BoxScrollView {
PageView({
Key key,
Axis scrollDirection: Axis.horizontal,
bool reverse: false,
PageController controller,
ScrollPhysics physics: const PageScrollPhysics(),
bool shrinkWrap: false,
EdgeInsets padding,
this.onPageChanged,
List<Widget> children: const <Widget>[],
}) : childrenDelegate = new SliverChildListDelegate(children), super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller ?? _defaultPageController,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
);
PageView.builder({
Key key,
Axis scrollDirection: Axis.horizontal,
bool reverse: false,
PageController controller,
ScrollPhysics physics: const PageScrollPhysics(),
bool shrinkWrap: false,
EdgeInsets padding,
this.onPageChanged,
IndexedWidgetBuilder itemBuilder,
int itemCount,
}) : childrenDelegate = new SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller ?? _defaultPageController,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
);
PageView.custom({
Key key,
Axis scrollDirection: Axis.horizontal,
bool reverse: false,
PageController controller,
ScrollPhysics physics: const PageScrollPhysics(),
bool shrinkWrap: false,
EdgeInsets padding,
this.onPageChanged,
@required this.childrenDelegate,
}) : super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller ?? _defaultPageController,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
) {
assert(childrenDelegate != null);
}
final ValueChanged<int> onPageChanged;
final SliverChildDelegate childrenDelegate;
@override
Widget buildChildLayout(BuildContext context) {
return new SliverFill(delegate: childrenDelegate);
}
@override
Widget build(BuildContext context) {
final Widget scrollable = super.build(context);
return new NotificationListener<ScrollNotification2>(
onNotification: (ScrollNotification2 notification) {
if (notification.depth == 1 && onPageChanged != null && notification is ScrollEndNotification) {
final ScrollableMetrics metrics = notification.metrics;
onPageChanged(metrics.extentBefore ~/ metrics.viewportDimension);
}
return true;
},
child: scrollable,
);
}
}
......@@ -11,24 +11,25 @@ import 'scroll_position.dart';
class ScrollController {
ScrollController({
this.initialScrollOffset,
this.initialScrollOffset: 0.0,
});
/// The initial value to use for [offset].
///
/// If [initialScrollOffset] is non-null, new [ScrollPosition] objects that
/// are created and attached to this controller will have their offset
/// initialized to this value.
/// New [ScrollPosition] objects that are created and attached to this
/// controller will have their offset initialized to this value.
final double initialScrollOffset;
final List<ScrollPosition> _positions = <ScrollPosition>[];
double get offset {
ScrollPosition get position {
assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
assert(_positions.length == 1, 'ScrollController attached to multiple scroll views.');
return _positions.single.pixels;
return _positions.single;
}
double get offset => position.pixels;
/// Animates the position from its current value to the given value.
///
/// Any active animation is canceled. If the user is currently scrolling, that
......@@ -57,11 +58,7 @@ class ScrollController {
Future<Null> animateTo(double offset, {
@required Duration duration,
@required Curve curve,
}) {
assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
assert(_positions.length == 1, 'ScrollController attached to multiple scroll views.');
return _positions.single.animateTo(offset, duration: duration, curve: curve);
}
}) => position.animateTo(offset, duration: duration, curve: curve);
/// Jumps the scroll position from its current value to the given value,
/// without animation, and without checking if the new value is in range.
......@@ -75,11 +72,7 @@ class ScrollController {
///
/// Immediately after the jump, a ballistic activity is started, in case the
/// value was out of range.
void jumpTo(double value) {
assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
assert(_positions.length == 1, 'ScrollController attached to multiple scroll views.');
_positions.single.jumpTo(value);
}
void jumpTo(double value) => position.jumpTo(value);
/// Register the given position with this controller.
///
......@@ -111,7 +104,7 @@ class ScrollController {
return new ScrollPosition(
physics: physics,
state: state,
offset: initialScrollOffset,
initialPixels: initialScrollOffset,
oldPosition: oldPosition,
);
}
......
......@@ -26,35 +26,26 @@ class ScrollableMetrics {
@required this.extentBefore,
@required this.extentInside,
@required this.extentAfter,
@required this.viewportDimension,
});
/// The quantity of content conceptually "above" the currently visible content
/// of the viewport in the scrollable. This is the content above the content
/// described by [extentInside].
///
/// The units are in general arbitrary, and decided by the [ScrollPosition]
/// that generated the [ScrollableMetrics]. They will be the same units as for
/// [extentInside] and [extentAfter].
final double extentBefore;
/// The quantity of visible content. If [extentBefore] and [extentAfter] are
/// non-zero, then this is typically the height of the viewport. It could be
/// less if there is less content visible than the size of the viewport.
///
/// The units are in general arbitrary, and decided by the [ScrollPosition]
/// that generated the [ScrollableMetrics]. They will be the same units as for
/// [extentBefore] and [extentAfter].
final double extentInside;
/// The quantity of content conceptually "below" the currently visible content
/// of the viewport in the scrollable. This is the content below the content
/// described by [extentInside].
///
/// The units are in general arbitrary, and decided by the [ScrollPosition]
/// that generated the [ScrollableMetrics]. They will be the same units as for
/// [extentBefore] and [extentInside].
final double extentAfter;
final double viewportDimension;
@override
String toString() {
return '$runtimeType(${extentBefore.toStringAsFixed(1)}..[${extentInside.toStringAsFixed(1)}]..${extentAfter.toStringAsFixed(1)}})';
......
......@@ -107,13 +107,12 @@ class ScrollPosition extends ViewportOffset {
ScrollPosition({
@required this.physics,
@required this.state,
double offset: 0.0,
double initialPixels: 0.0,
ScrollPosition oldPosition,
}) : _pixels = offset ?? 0.0 {
}) : _pixels = initialPixels {
assert(physics != null);
assert(state != null);
assert(state.vsync != null);
assert(pixels != null);
if (oldPosition != null)
absorb(oldPosition);
if (activity == null)
......@@ -234,6 +233,7 @@ class ScrollPosition extends ViewportOffset {
extentBefore: math.max(pixels - minScrollExtent, 0.0),
extentInside: math.min(pixels, maxScrollExtent) - math.max(pixels, minScrollExtent) + math.min(viewportDimension, maxScrollExtent - minScrollExtent),
extentAfter: math.max(maxScrollExtent - pixels, 0.0),
viewportDimension: viewportDimension,
);
}
......@@ -289,6 +289,11 @@ class ScrollPosition extends ViewportOffset {
return 0.0;
}
@protected
void correctPixels(double value) {
_pixels = value;
}
@override
void correctBy(double correction) {
_pixels += correction;
......@@ -316,7 +321,7 @@ class ScrollPosition extends ViewportOffset {
bool _didChangeViewportDimension = true;
@override
void applyViewportDimension(double viewportDimension) {
bool applyViewportDimension(double viewportDimension) {
if (_viewportDimension != viewportDimension) {
_viewportDimension = viewportDimension;
_didChangeViewportDimension = true;
......@@ -325,6 +330,7 @@ class ScrollPosition extends ViewportOffset {
// relies on both values being computed into applyContentDimensions.
}
state.setCanDrag(canDrag);
return true;
}
@override
......
......@@ -369,57 +369,3 @@ class GridView extends BoxScrollView {
);
}
}
/// A scrollable list that works page by page.
// TODO(ianh): More documentation here.
///
/// See also:
///
/// * [SingleChildScrollView], when you need to make a single child scrollable.
/// * [ListView], for a scrollable list of boxes.
/// * [GridView], for a scrollable grid of boxes.
class PageView extends BoxScrollView {
PageView({
Key key,
Axis scrollDirection: Axis.horizontal,
bool reverse: false,
// TODO(abarth): Add PageController that knows about pages.
ScrollPhysics physics: const PageScrollPhysics(),
bool shrinkWrap: false,
EdgeInsets padding,
List<Widget> children: const <Widget>[],
}) : childrenDelegate = new SliverChildListDelegate(children), super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
);
PageView.custom({
Key key,
Axis scrollDirection: Axis.horizontal,
bool reverse: false,
ScrollPhysics physics: const PageScrollPhysics(),
bool shrinkWrap: false,
EdgeInsets padding,
@required this.childrenDelegate,
}) : super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
) {
assert(childrenDelegate != null);
}
final SliverChildDelegate childrenDelegate;
@override
Widget buildChildLayout(BuildContext context) {
return new SliverFill(delegate: childrenDelegate);
}
}
......@@ -159,8 +159,9 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
assert(_minScrollExtent != null);
assert(_maxScrollExtent != null);
assert(_effectiveExtent != null);
offset.applyViewportDimension(_effectiveExtent);
if (offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent))
final bool didAcceptViewportDimensions = offset.applyViewportDimension(_effectiveExtent);
final bool didAcceptContentDimensions = offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
if (didAcceptViewportDimensions || didAcceptContentDimensions)
markNeedsPaint();
}
}
......
......@@ -39,6 +39,7 @@ export 'src/widgets/orientation_builder.dart';
export 'src/widgets/overlay.dart';
export 'src/widgets/overscroll_indicator.dart';
export 'src/widgets/page_storage.dart';
export 'src/widgets/page_view.dart';
export 'src/widgets/pageable_list.dart';
export 'src/widgets/pages.dart';
export 'src/widgets/performance_overlay.dart';
......
......@@ -62,8 +62,7 @@ void main() {
await tester.pump(const Duration(seconds: 2));
await tester.tapAt(const Point(380.0, 20.0));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 100));
expect(_selectedDate, equals(new DateTime(2016, DateTime.JULY, 1)));
await tester.tapAt(const Point(300.0, 100.0));
......@@ -71,8 +70,7 @@ void main() {
await tester.pump(const Duration(seconds: 2));
await tester.scroll(find.byKey(_datePickerKey), const Offset(-300.0, 0.0));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 100));
expect(_selectedDate, equals(new DateTime(2016, DateTime.AUGUST, 5)));
await tester.tapAt(const Point(45.0, 270.0));
......@@ -80,14 +78,11 @@ void main() {
await tester.pump(const Duration(seconds: 2));
await tester.scroll(find.byKey(_datePickerKey), const Offset(300.0, 0.0));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 100));
expect(_selectedDate, equals(new DateTime(2016, DateTime.SEPTEMBER, 25)));
await tester.tapAt(const Point(210.0, 180.0));
expect(_selectedDate, equals(new DateTime(2016, DateTime.AUGUST, 17)));
await tester.pump(const Duration(seconds: 2));
});
testWidgets('render picker with intrinsic dimensions', (WidgetTester tester) async {
......
......@@ -108,4 +108,87 @@ void main() {
expect(leftOf(0), equals(-100.0));
expect(sizeOf(0), equals(const Size(800.0, 600.0)));
});
testWidgets('PageController control test', (WidgetTester tester) async {
PageController controller = new PageController(initialPage: 4.0);
await tester.pumpWidget(new Center(
child: new SizedBox(
width: 600.0,
height: 400.0,
child: new PageView(
controller: controller,
children: kStates.map<Widget>((String state) => new Text(state)).toList(),
),
),
));
expect(find.text('California'), findsOneWidget);
controller.nextPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 100));
expect(find.text('Colorado'), findsOneWidget);
await tester.pumpWidget(new Center(
child: new SizedBox(
width: 300.0,
height: 400.0,
child: new PageView(
controller: controller,
children: kStates.map<Widget>((String state) => new Text(state)).toList(),
),
),
));
expect(find.text('Colorado'), findsOneWidget);
controller.previousPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 100));
expect(find.text('California'), findsOneWidget);
});
testWidgets('PageController page stability', (WidgetTester tester) async {
await tester.pumpWidget(new Center(
child: new SizedBox(
width: 600.0,
height: 400.0,
child: new PageView(
children: kStates.map<Widget>((String state) => new Text(state)).toList(),
),
),
));
expect(find.text('Alabama'), findsOneWidget);
await tester.scroll(find.byType(PageView), const Offset(-1250.0, 0.0));
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 100));
expect(find.text('Arizona'), findsOneWidget);
await tester.pumpWidget(new Center(
child: new SizedBox(
width: 250.0,
height: 100.0,
child: new PageView(
children: kStates.map<Widget>((String state) => new Text(state)).toList(),
),
),
));
expect(find.text('Arizona'), findsOneWidget);
await tester.pumpWidget(new Center(
child: new SizedBox(
width: 450.0,
height: 400.0,
child: new PageView(
children: kStates.map<Widget>((String state) => new Text(state)).toList(),
),
),
));
expect(find.text('Arizona'), findsOneWidget);
});
}
......@@ -22,7 +22,7 @@ class TestScrollPosition extends ScrollPosition {
@override
TestScrollPhysics get physics => super.physics;
double _min, _viewport, _max, _pixels;
double _pixels;
@override
double get pixels => _pixels;
......@@ -40,35 +40,24 @@ class TestScrollPosition extends ScrollPosition {
_pixels += correction;
}
@override
void applyViewportDimension(double viewportDimension) {
_viewport = viewportDimension;
super.applyViewportDimension(viewportDimension);
}
@override
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
_min = minScrollExtent;
_max = maxScrollExtent;
return super.applyContentDimensions(minScrollExtent, maxScrollExtent);
}
@override
ScrollableMetrics getMetrics() {
double insideExtent = _viewport;
double beforeExtent = _pixels - _min;
double afterExtent = _max - _pixels;
double insideExtent = viewportDimension;
double beforeExtent = _pixels - minScrollExtent;
double afterExtent = maxScrollExtent - _pixels;
if (insideExtent > 0.0) {
return new ScrollableMetrics(
extentBefore: physics.extentMultiplier * beforeExtent / insideExtent,
extentInside: physics.extentMultiplier,
extentAfter: physics.extentMultiplier * afterExtent / insideExtent,
viewportDimension: viewportDimension,
);
} else {
return new ScrollableMetrics(
extentBefore: 0.0,
extentInside: 0.0,
extentAfter: 0.0,
viewportDimension: viewportDimension,
);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment