Commit 3b3d5983 authored by Hans Muller's avatar Hans Muller

Merge pull request #820 from HansMuller/pageable_list

Revised PageableList et al

An itemExtent-computing SizeObserver is no longer needed to use PageableList. The PageableList just uses its own size as the itemExtent.

Added the itemsSnapAlignment property to PageableList which enables snapping scrolls to an adjacent item (the default), or any item boundary no not at all.

PageableList scrollOffsets now vary from 0.0 to itemCount instead of 0.0 to itemExtent * itemCount. Using logical coordinates instead of pixel coordinates means that the scroll position is insensitive to changes in the PageablList's size.

Added HomogenousPageViewport which is used by PageableList. HomogenousPageViewport scrollOffsets are defined as for PageableList.

Factored the (substantial) common parts of HomogenousViewport HomogenousPageViewport into a file private _ViewportBase class.

Removed PageableWidgetList. PageableList now just extends Scrollable. Moved PageableList into its own file.

Removed the pixel dependencies from ScrollBehavior. ScrollBehavior.createFlingSimulation() no longer sets the simulation's tolerance. The caller must do this instead.

Scrollable now uses pixelToScrollOffset() to convert from input gesture positions and velocities to scrollOffsets.

Fixes #710
parents 4f22f3ec c1d42a2f
......@@ -120,18 +120,11 @@ class PageableListAppState extends State<PageableListApp> {
}
Widget _buildBody(BuildContext context) {
Widget list = new PageableList<CardModel>(
return new PageableList<CardModel>(
items: cardModels,
itemsWrap: itemsWrap,
itemBuilder: buildCard,
scrollDirection: scrollDirection,
itemExtent: (scrollDirection == ScrollDirection.vertical)
? pageSize.height
: pageSize.width
);
return new SizeObserver(
onSizeChanged: updatePageSize,
child: list
scrollDirection: scrollDirection
);
}
......
......@@ -3,20 +3,13 @@
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:newton/newton.dart';
const double _kSecondsPerMillisecond = 1000.0;
const double _kScrollDrag = 0.025;
// TODO(abarth): These values won't work well if there's a scale transform.
final Tolerance _kDefaultScrollTolerance = new Tolerance(
velocity: 1.0 / (0.050 * ui.window.devicePixelRatio), // logical pixels per second
distance: 1.0 / ui.window.devicePixelRatio // logical pixels
);
/// An interface for controlling the behavior of scrollable widgets
/// An interface for controlling the behavior of scrollable widgets.
abstract class ScrollBehavior {
/// Called when a drag gesture ends. Returns a simulation that
/// propels the scrollOffset.
......@@ -24,10 +17,10 @@ abstract class ScrollBehavior {
/// Called when a drag gesture ends and toSnapOffset is specified.
/// Returns an animation that ends at the snap offset.
Simulation createSnapScrollSimulation(double startOffset, double endOffset, double velocity) => null;
Simulation createSnapScrollSimulation(double startOffset, double endOffset, double startVelocity, double endVelocity) => null;
/// Return the scroll offset to use when the user attempts to scroll
/// from the given offset by the given delta
/// from the given offset by the given delta.
double applyCurve(double scrollOffset, double scrollDelta);
/// Whether this scroll behavior currently permits scrolling
......@@ -39,11 +32,11 @@ abstract class ExtentScrollBehavior extends ScrollBehavior {
ExtentScrollBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 })
: _contentExtent = contentExtent, _containerExtent = containerExtent;
/// The linear extent of the content inside the scrollable widget
/// The linear extent of the content inside the scrollable widget.
double get contentExtent => _contentExtent;
double _contentExtent;
/// The linear extent of the exterior of the scrollable widget
/// The linear extent of the exterior of the scrollable widget.
double get containerExtent => _containerExtent;
double _containerExtent;
......@@ -64,14 +57,14 @@ abstract class ExtentScrollBehavior extends ScrollBehavior {
return scrollOffset.clamp(minScrollOffset, maxScrollOffset);
}
/// The minimum value the scroll offset can obtain
/// The minimum value the scroll offset can obtain.
double get minScrollOffset;
/// The maximum value the scroll offset can obatin
/// The maximum value the scroll offset can obtain.
double get maxScrollOffset;
}
/// A scroll behavior that prevents the user from exeeding scroll bounds
/// A scroll behavior that prevents the user from exeeding scroll bounds.
class BoundedBehavior extends ExtentScrollBehavior {
BoundedBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 })
: super(contentExtent: contentExtent, containerExtent: containerExtent);
......@@ -84,7 +77,18 @@ class BoundedBehavior extends ExtentScrollBehavior {
}
}
/// A scroll behavior that does not prevent the user from exeeding scroll bounds
Simulation _createFlingScrollSimulation(double position, double velocity, double minScrollOffset, double maxScrollOffset) {
final double startVelocity = velocity * _kSecondsPerMillisecond;
final SpringDescription spring = new SpringDescription.withDampingRatio(mass: 1.0, springConstant: 170.0, ratio: 1.1);
return new ScrollSimulation(position, startVelocity, minScrollOffset, maxScrollOffset, spring, _kScrollDrag);
}
Simulation _createSnapScrollSimulation(double startOffset, double endOffset, double startVelocity, double endVelocity) {
final double velocity = startVelocity * _kSecondsPerMillisecond;
return new FrictionSimulation.through(startOffset, endOffset, velocity, endVelocity);
}
/// A scroll behavior that does not prevent the user from exeeding scroll bounds.
class UnboundedBehavior extends ExtentScrollBehavior {
UnboundedBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 })
: super(contentExtent: contentExtent, containerExtent: containerExtent);
......@@ -96,8 +100,8 @@ class UnboundedBehavior extends ExtentScrollBehavior {
);
}
Simulation createSnapScrollSimulation(double startOffset, double endOffset, double velocity) {
return _createSnapScrollSimulation(startOffset, endOffset, velocity);
Simulation createSnapScrollSimulation(double startOffset, double endOffset, double startVelocity, double endVelocity) {
return _createSnapScrollSimulation(startOffset, endOffset, startVelocity, endVelocity);
}
double get minScrollOffset => double.NEGATIVE_INFINITY;
......@@ -108,33 +112,17 @@ class UnboundedBehavior extends ExtentScrollBehavior {
}
}
Simulation _createFlingScrollSimulation(double position, double velocity, double minScrollOffset, double maxScrollOffset, Tolerance tolerance) {
double startVelocity = velocity * _kSecondsPerMillisecond;
SpringDescription spring = new SpringDescription.withDampingRatio(mass: 1.0, springConstant: 170.0, ratio: 1.1);
ScrollSimulation simulation =
new ScrollSimulation(position, startVelocity, minScrollOffset, maxScrollOffset, spring, _kScrollDrag)
..tolerance = tolerance ?? _kDefaultScrollTolerance;
return simulation;
}
Simulation _createSnapScrollSimulation(double startOffset, double endOffset, double velocity) {
double startVelocity = velocity * _kSecondsPerMillisecond;
double endVelocity = velocity.sign * _kDefaultScrollTolerance.velocity;
return new FrictionSimulation.through(startOffset, endOffset, startVelocity, endVelocity);
}
/// A scroll behavior that lets the user scroll beyond the scroll bounds with some resistance
/// A scroll behavior that lets the user scroll beyond the scroll bounds with some resistance.
class OverscrollBehavior extends BoundedBehavior {
OverscrollBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 })
: super(contentExtent: contentExtent, containerExtent: containerExtent);
Simulation createFlingScrollSimulation(double position, double velocity, { Tolerance tolerance }) {
return _createFlingScrollSimulation(position, velocity, minScrollOffset, maxScrollOffset, tolerance);
Simulation createFlingScrollSimulation(double position, double velocity) {
return _createFlingScrollSimulation(position, velocity, minScrollOffset, maxScrollOffset);
}
Simulation createSnapScrollSimulation(double startOffset, double endOffset, double velocity) {
return _createSnapScrollSimulation(startOffset, endOffset, velocity);
Simulation createSnapScrollSimulation(double startOffset, double endOffset, double startVelocity, double endVelocity) {
return _createSnapScrollSimulation(startOffset, endOffset, startVelocity, endVelocity);
}
double applyCurve(double scrollOffset, double scrollDelta) {
......@@ -154,13 +142,13 @@ class OverscrollBehavior extends BoundedBehavior {
}
}
/// A scroll behavior that lets the user scroll beyond the scroll bounds only when the bounds are disjoint
/// A scroll behavior that lets the user scroll beyond the scroll bounds only when the bounds are disjoint.
class OverscrollWhenScrollableBehavior extends OverscrollBehavior {
bool get isScrollable => contentExtent > containerExtent;
Simulation createFlingScrollSimulation(double position, double velocity, { Tolerance tolerance }) {
Simulation createFlingScrollSimulation(double position, double velocity) {
if (isScrollable || position < minScrollOffset || position > maxScrollOffset)
return super.createFlingScrollSimulation(position, velocity, tolerance: tolerance);
return super.createFlingScrollSimulation(position, velocity);
return null;
}
......
......@@ -105,7 +105,8 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGest
Offset velocity = tracker.getVelocity();
if (velocity != null && _isFlingGesture(velocity))
onEnd(velocity);
onEnd(Offset.zero);
else
onEnd(Offset.zero);
}
_velocityTrackers.clear();
}
......
// Copyright 2015 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 'dart:math' as math;
import 'package:flutter/animation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'framework.dart';
import 'homogeneous_viewport.dart';
import 'scrollable.dart';
enum ItemsSnapAlignment { item, adjacentItem }
typedef void PageChangedCallback(int newPage);
class PageableList<T> extends Scrollable {
PageableList({
Key key,
initialScrollOffset,
ScrollDirection scrollDirection: ScrollDirection.vertical,
ScrollListener onScrollStart,
ScrollListener onScroll,
ScrollListener onScrollEnd,
SnapOffsetCallback snapOffsetCallback,
double snapAlignmentOffset: 0.0,
this.items,
this.itemBuilder,
this.itemsWrap: false,
this.itemsSnapAlignment: ItemsSnapAlignment.adjacentItem,
this.onPageChanged,
this.scrollableListPainter,
this.duration: const Duration(milliseconds: 200),
this.curve: Curves.ease
}) : super(
key: key,
initialScrollOffset: initialScrollOffset,
scrollDirection: scrollDirection,
onScrollStart: onScrollStart,
onScroll: onScroll,
onScrollEnd: onScrollEnd,
snapOffsetCallback: snapOffsetCallback,
snapAlignmentOffset: snapAlignmentOffset
);
final List<T> items;
final ItemBuilder<T> itemBuilder;
final ItemsSnapAlignment itemsSnapAlignment;
final bool itemsWrap;
final PageChangedCallback onPageChanged;
final ScrollableListPainter scrollableListPainter;
final Duration duration;
final Curve curve;
PageableListState<T, PageableList<T>> createState() => new PageableListState<T, PageableList<T>>();
}
class PageableListState<T, Config extends PageableList<T>> extends ScrollableState<Config> {
int get itemCount => config.items?.length ?? 0;
int _previousItemCount;
double pixelToScrollOffset(double value) {
final RenderBox box = context.findRenderObject();
if (box == null || !box.hasSize)
return 0.0;
final double pixelScrollExtent = config.scrollDirection == ScrollDirection.vertical ? box.size.height : box.size.width;
return pixelScrollExtent == 0.0 ? 0.0 : value / pixelScrollExtent;
}
void didUpdateConfig(Config oldConfig) {
super.didUpdateConfig(oldConfig);
bool scrollBehaviorUpdateNeeded = config.scrollDirection != oldConfig.scrollDirection;
if (config.itemsWrap != oldConfig.itemsWrap)
scrollBehaviorUpdateNeeded = true;
if (itemCount != _previousItemCount) {
_previousItemCount = itemCount;
scrollBehaviorUpdateNeeded = true;
}
if (scrollBehaviorUpdateNeeded)
_updateScrollBehavior();
}
void _updateScrollBehavior() {
// if you don't call this from build(), you must call it from setState().
if (config.scrollableListPainter != null)
config.scrollableListPainter.contentExtent = itemCount.toDouble();
scrollTo(scrollBehavior.updateExtents(
contentExtent: itemCount.toDouble(),
containerExtent: 1.0,
scrollOffset: scrollOffset
));
}
void dispatchOnScrollStart() {
super.dispatchOnScrollStart();
config.scrollableListPainter?.scrollStarted();
}
void dispatchOnScroll() {
super.dispatchOnScroll();
if (config.scrollableListPainter != null)
config.scrollableListPainter.scrollOffset = scrollOffset;
}
void dispatchOnScrollEnd() {
super.dispatchOnScrollEnd();
config.scrollableListPainter?.scrollEnded();
}
Widget buildContent(BuildContext context) {
if (itemCount != _previousItemCount) {
_previousItemCount = itemCount;
_updateScrollBehavior();
}
return new HomogeneousPageViewport(
builder: buildItems,
itemsWrap: config.itemsWrap,
itemCount: itemCount,
direction: config.scrollDirection,
startOffset: scrollOffset,
overlayPainter: config.scrollableListPainter
);
}
ScrollBehavior createScrollBehavior() {
return config.itemsWrap ? new UnboundedBehavior() : new OverscrollBehavior();
}
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
bool get snapScrollOffsetChanges => config.itemsSnapAlignment == ItemsSnapAlignment.item;
double snapScrollOffset(double newScrollOffset) {
double previousItemOffset = newScrollOffset.floorToDouble();
double nextItemOffset = newScrollOffset.ceilToDouble();
return (newScrollOffset - previousItemOffset < 0.5 ? previousItemOffset : nextItemOffset)
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
}
Future _flingToAdjacentItem(Offset velocity) {
double scrollVelocity = scrollDirectionVelocity(velocity);
double newScrollOffset = snapScrollOffset(scrollOffset + scrollVelocity.sign)
.clamp(snapScrollOffset(scrollOffset - 0.5), snapScrollOffset(scrollOffset + 0.5));
return scrollTo(newScrollOffset, duration: config.duration, curve: config.curve)
.then(_notifyPageChanged);
}
Future fling(Offset velocity) {
switch(config.itemsSnapAlignment) {
case ItemsSnapAlignment.adjacentItem:
return _flingToAdjacentItem(velocity);
default:
return super.fling(velocity).then(_notifyPageChanged);
}
}
Future settleScrollOffset() {
return scrollTo(snapScrollOffset(scrollOffset), duration: config.duration, curve: config.curve)
.then(_notifyPageChanged);
}
List<Widget> buildItems(BuildContext context, int start, int count) {
List<Widget> result = new List<Widget>();
int begin = config.itemsWrap ? start : math.max(0, start);
int end = config.itemsWrap ? begin + count : math.min(begin + count, config.items.length);
for (int i = begin; i < end; ++i)
result.add(config.itemBuilder(context, config.items[i % itemCount], i));
assert(result.every((Widget item) => item.key != null));
return result;
}
void _notifyPageChanged(_) {
if (config.onPageChanged != null)
config.onPageChanged(itemCount == 0 ? 0 : scrollOffset.floor() % itemCount);
}
}
......@@ -24,6 +24,11 @@ const double _kMillisecondsPerSecond = 1000.0;
const double _kMinFlingVelocity = -kMaxFlingVelocity * _kMillisecondsPerSecond;
const double _kMaxFlingVelocity = kMaxFlingVelocity * _kMillisecondsPerSecond;
final Tolerance kPixelScrollTolerance = new Tolerance(
velocity: 1.0 / (0.050 * ui.window.devicePixelRatio), // logical pixels per second
distance: 1.0 / ui.window.devicePixelRatio // logical pixels
);
typedef void ScrollListener(double scrollOffset);
typedef double SnapOffsetCallback(double scrollOffset);
......@@ -122,6 +127,18 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
return new Offset(0.0, scrollOffset);
}
/// Convert a position or velocity measured in terms of pixels to a scrollOffset.
/// Scrollable gesture handlers convert their incoming values with this method.
/// Subclasses that define scrollOffset in units other than pixels must
/// override this method.
double pixelToScrollOffset(double pixelValue) => pixelValue;
double scrollDirectionVelocity(Offset scrollVelocity) {
return config.scrollDirection == ScrollDirection.horizontal
? -scrollVelocity.dx
: -scrollVelocity.dy;
}
ScrollBehavior _scrollBehavior;
ScrollBehavior createScrollBehavior();
ScrollBehavior get scrollBehavior {
......@@ -180,11 +197,32 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
}
Simulation _createFlingSimulation(double velocity) {
return scrollBehavior.createFlingScrollSimulation(scrollOffset, velocity);
/*
// Assume that we're rendering at atleast 15 FPS. Stop when we're
// scrolling less than one logical pixel per frame. We're essentially
// normalizing by the devicePixelRatio so that the threshold has the
// same effect independent of the device's pixel density.
double endVelocity = pixelToScrollOffset(15.0 * ui.window.devicePixelRatio);
// Similar to endVelocity. Stop scrolling when we're this close to
// destiniation scroll offset.
double endDistance = pixelToScrollOffset(0.5 * ui.window.devicePixelRatio);
*/
final double endVelocity = pixelToScrollOffset(kPixelScrollTolerance.velocity);
final double endDistance = pixelToScrollOffset(kPixelScrollTolerance.distance);
return scrollBehavior.createFlingScrollSimulation(scrollOffset, velocity)
..tolerance = new Tolerance(velocity: endVelocity.abs(), distance: endDistance);
}
double snapScrollOffset(double value) {
return config.snapOffsetCallback == null ? value : config.snapOffsetCallback(value);
}
bool get snapScrollOffsetChanges => config.snapOffsetCallback != null;
Simulation _createSnapSimulation(double velocity) {
if (velocity == null || config.snapOffsetCallback == null || !_scrollOffsetIsInBounds(scrollOffset))
if (!snapScrollOffsetChanges || velocity == 0.0 || !_scrollOffsetIsInBounds(scrollOffset))
return null;
Simulation simulation = _createFlingSimulation(velocity);
......@@ -195,14 +233,15 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
if (endScrollOffset.isNaN)
return null;
double snappedScrollOffset = config.snapOffsetCallback(endScrollOffset + config.snapAlignmentOffset);
double snappedScrollOffset = snapScrollOffset(endScrollOffset + config.snapAlignmentOffset);
double alignedScrollOffset = snappedScrollOffset - config.snapAlignmentOffset;
if (!_scrollOffsetIsInBounds(alignedScrollOffset))
return null;
double snapVelocity = velocity.abs() * (alignedScrollOffset - scrollOffset).sign;
double endVelocity = pixelToScrollOffset(kPixelScrollTolerance.velocity * velocity.sign);
Simulation toSnapSimulation =
scrollBehavior.createSnapScrollSimulation(scrollOffset, alignedScrollOffset, snapVelocity);
scrollBehavior.createSnapScrollSimulation(scrollOffset, alignedScrollOffset, snapVelocity, endVelocity);
if (toSnapSimulation == null)
return null;
......@@ -211,10 +250,10 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
return new ClampedSimulation(toSnapSimulation, xMin: offsetMin, xMax: offsetMax);
}
Future _startToEndAnimation({ double velocity }) {
Future _startToEndAnimation(Offset scrollVelocity) {
double velocity = scrollDirectionVelocity(scrollVelocity);
_animation.stop();
Simulation simulation =
_createSnapSimulation(velocity) ?? _createFlingSimulation(velocity ?? 0.0);
Simulation simulation = _createSnapSimulation(velocity) ?? _createFlingSimulation(velocity);
if (simulation == null)
return new Future.value();
return _animation.animateWith(simulation);
......@@ -254,16 +293,16 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
return scrollTo(newScrollOffset, duration: duration, curve: curve);
}
Future fling(Offset velocity) {
if (velocity != Offset.zero)
return _startToEndAnimation(velocity: _scrollVelocity(velocity));
Future fling(Offset scrollVelocity) {
if (scrollVelocity != Offset.zero)
return _startToEndAnimation(scrollVelocity);
if (!_animation.isAnimating)
return settleScrollOffset();
return new Future.value();
}
Future settleScrollOffset() {
return _startToEndAnimation();
return _startToEndAnimation(Offset.zero);
}
void dispatchOnScrollStart() {
......@@ -282,13 +321,6 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
config.onScrollEnd(_scrollOffset);
}
double _scrollVelocity(ui.Offset velocity) {
double scrollVelocity = config.scrollDirection == ScrollDirection.horizontal
? -velocity.dx
: -velocity.dy;
return scrollVelocity.clamp(_kMinFlingVelocity, _kMaxFlingVelocity) / _kMillisecondsPerSecond;
}
void _handlePointerDown(_) {
_animation.stop();
}
......@@ -300,11 +332,16 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
void _handleDragUpdate(double delta) {
// We negate the delta here because a positive scroll offset moves the
// the content up (or to the left) rather than down (or the right).
scrollBy(-delta);
scrollBy(pixelToScrollOffset(-delta));
}
Future _handleDragEnd(Offset velocity) {
return fling(velocity).then((_) {
double _toScrollVelocity(double velocity) {
return pixelToScrollOffset(velocity.clamp(_kMinFlingVelocity, _kMaxFlingVelocity) / _kMillisecondsPerSecond);
}
Future _handleDragEnd(Offset pixelScrollVelocity) {
final Offset scrollVelocity = new Offset(_toScrollVelocity(pixelScrollVelocity.dx), _toScrollVelocity(pixelScrollVelocity.dy));
return fling(scrollVelocity).then((_) {
dispatchOnScrollEnd();
});
}
......@@ -690,71 +727,6 @@ class ScrollableListState<T, Config extends ScrollableList<T>> extends Scrollabl
}
}
typedef void PageChangedCallback(int newPage);
class PageableList<T> extends ScrollableList<T> {
PageableList({
Key key,
int initialPage,
ScrollDirection scrollDirection: ScrollDirection.horizontal,
ScrollListener onScroll,
List<T> items,
ItemBuilder<T> itemBuilder,
bool itemsWrap: false,
double itemExtent,
this.onPageChanged,
EdgeDims padding,
this.duration: const Duration(milliseconds: 200),
this.curve: Curves.ease
}) : super(
key: key,
initialScrollOffset: initialPage == null ? null : initialPage * itemExtent,
scrollDirection: scrollDirection,
onScroll: onScroll,
items: items,
itemBuilder: itemBuilder,
itemsWrap: itemsWrap,
itemExtent: itemExtent,
padding: padding
);
final Duration duration;
final Curve curve;
final PageChangedCallback onPageChanged;
PageableListState<T> createState() => new PageableListState<T>();
}
class PageableListState<T> extends ScrollableListState<T, PageableList<T>> {
double _snapScrollOffset(double newScrollOffset) {
double scaledScrollOffset = newScrollOffset / config.itemExtent;
double previousScrollOffset = scaledScrollOffset.floor() * config.itemExtent;
double nextScrollOffset = scaledScrollOffset.ceil() * config.itemExtent;
double delta = newScrollOffset - previousScrollOffset;
return (delta < config.itemExtent / 2.0 ? previousScrollOffset : nextScrollOffset)
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
}
Future fling(ui.Offset velocity) {
double scrollVelocity = _scrollVelocity(velocity);
double newScrollOffset = _snapScrollOffset(scrollOffset + scrollVelocity.sign * config.itemExtent)
.clamp(_snapScrollOffset(scrollOffset - config.itemExtent / 2.0),
_snapScrollOffset(scrollOffset + config.itemExtent / 2.0));
return scrollTo(newScrollOffset, duration: config.duration, curve: config.curve).then(_notifyPageChanged);
}
int get currentPage => (scrollOffset / config.itemExtent).floor() % itemCount;
void _notifyPageChanged(_) {
if (config.onPageChanged != null)
config.onPageChanged(currentPage);
}
Future settleScrollOffset() {
return scrollTo(_snapScrollOffset(scrollOffset), duration: config.duration, curve: config.curve).then(_notifyPageChanged);
}
}
/// A general scrollable list for a large number of children that might not all
/// have the same height. Prefer [ScrollableWidgetList] when all the children
/// have the same height because it can use that property to be more efficient.
......
......@@ -28,6 +28,7 @@ export 'src/widgets/notification_listener.dart';
export 'src/widgets/overlay.dart';
export 'src/widgets/page_storage.dart';
export 'src/widgets/pages.dart';
export 'src/widgets/pageable_list.dart';
export 'src/widgets/placeholder.dart';
export 'src/widgets/routes.dart';
export 'src/widgets/scrollable.dart';
......
......@@ -27,7 +27,6 @@ Widget buildFrame() {
items: pages,
itemBuilder: buildPage,
itemsWrap: itemsWrap,
itemExtent: pageSize.width,
scrollDirection: ScrollDirection.horizontal,
onPageChanged: (int page) { currentPage = page; }
);
......
......@@ -57,7 +57,7 @@ Future fling(double velocity) {
}
void main() {
test('ScrollableList snap scrolling, fling(-800)', () {
test('ScrollableList snap scrolling, fling(-0.8)', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(buildFrame());
......@@ -67,7 +67,7 @@ void main() {
Duration dt = const Duration(seconds: 2);
fling(-800.0);
fling(-0.8);
tester.pump(); // Start the scheduler at 0.0
tester.pump(dt);
expect(scrollOffset, closeTo(200.0, 1.0));
......@@ -76,7 +76,7 @@ void main() {
tester.pump();
expect(scrollOffset, 0.0);
fling(-2000.0);
fling(-2.0);
tester.pump();
tester.pump(dt);
expect(scrollOffset, closeTo(400.0, 1.0));
......@@ -85,7 +85,7 @@ void main() {
tester.pump();
expect(scrollOffset, 400.0);
fling(800.0);
fling(0.8);
tester.pump();
tester.pump(dt);
expect(scrollOffset, closeTo(0.0, 1.0));
......@@ -94,7 +94,7 @@ void main() {
tester.pump();
expect(scrollOffset, 800.0);
fling(2000.0);
fling(2.0);
tester.pump();
tester.pump(dt);
expect(scrollOffset, closeTo(200.0, 1.0));
......@@ -104,7 +104,7 @@ void main() {
expect(scrollOffset, 800.0);
bool completed = false;
fling(2000.0).then((_) {
fling(2.0).then((_) {
completed = true;
expect(scrollOffset, closeTo(200.0, 1.0));
});
......
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