Commit c06995a3 authored by Hans Muller's avatar Hans Muller

Clamp the snapped scroll simulation; Scrollable settle() and fling() return a Future

parent 946da101
...@@ -10,6 +10,7 @@ library animation; ...@@ -10,6 +10,7 @@ library animation;
export 'src/animation/animated_simulation.dart'; export 'src/animation/animated_simulation.dart';
export 'src/animation/animated_value.dart'; export 'src/animation/animated_value.dart';
export 'src/animation/animation_performance.dart'; export 'src/animation/animation_performance.dart';
export 'src/animation/clamped_simulation.dart';
export 'src/animation/curves.dart'; export 'src/animation/curves.dart';
export 'src/animation/forces.dart'; export 'src/animation/forces.dart';
export 'src/animation/scheduler.dart'; export 'src/animation/scheduler.dart';
......
...@@ -132,34 +132,45 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -132,34 +132,45 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
return offset >= behavior.minScrollOffset && offset < behavior.maxScrollOffset; return offset >= behavior.minScrollOffset && offset < behavior.maxScrollOffset;
} }
double _alignedScrollSnapOffset(double offset) { Simulation _createFlingSimulation(double velocity) {
return config.snapOffsetCallback(offset + config.snapAlignmentOffset) - config.snapAlignmentOffset; return scrollBehavior.createFlingScrollSimulation(scrollOffset, velocity);
} }
void _startToEndAnimation({ double velocity }) { Simulation _createSnapSimulation(double velocity) {
_stopAnimations(); if (velocity == null || config.snapOffsetCallback == null || !_scrollOffsetIsInBounds(scrollOffset))
return null;
if (velocity != null && config.snapOffsetCallback != null && _scrollOffsetIsInBounds(scrollOffset)) { Simulation simulation = _createFlingSimulation(velocity);
Simulation simulation = scrollBehavior.createFlingScrollSimulation(scrollOffset, velocity); if (simulation == null)
if (simulation == null) return null;
return;
double endScrollOffset = simulation.x(double.INFINITY); double endScrollOffset = simulation.x(double.INFINITY);
if (!endScrollOffset.isNaN) { if (endScrollOffset.isNaN)
double alignedScrollOffset = _alignedScrollSnapOffset(endScrollOffset); return null;
if (_scrollOffsetIsInBounds(alignedScrollOffset)) {
double snapVelocity = velocity.abs() * (alignedScrollOffset - scrollOffset).sign; double snappedScrollOffset = config.snapOffsetCallback(endScrollOffset + config.snapAlignmentOffset);
Simulation toSnapSimulation = scrollBehavior.createSnapScrollSimulation( double alignedScrollOffset = snappedScrollOffset - config.snapAlignmentOffset;
scrollOffset, alignedScrollOffset, snapVelocity); if (!_scrollOffsetIsInBounds(alignedScrollOffset))
_toEndAnimation.start(toSnapSimulation); return null;
return;
}
}
}
Simulation simulation = scrollBehavior.createFlingScrollSimulation(scrollOffset, velocity ?? 0.0); double snapVelocity = velocity.abs() * (alignedScrollOffset - scrollOffset).sign;
Simulation toSnapSimulation =
scrollBehavior.createSnapScrollSimulation(scrollOffset, alignedScrollOffset, snapVelocity);
if (toSnapSimulation == null)
return null;
double offsetMin = math.min(scrollOffset, alignedScrollOffset);
double offsetMax = math.max(scrollOffset, alignedScrollOffset);
return new ClampedSimulation(toSnapSimulation, xMin: offsetMin, xMax: offsetMax);
}
Future _startToEndAnimation({ double velocity }) {
_stopAnimations();
Simulation simulation =
_createSnapSimulation(velocity) ?? _createFlingSimulation(velocity ?? 0.0);
if (simulation == null) if (simulation == null)
return; return new Future.value();
_toEndAnimation.start(simulation); return _toEndAnimation.start(simulation);
} }
void dispose() { void dispose() {
...@@ -195,16 +206,16 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -195,16 +206,16 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
return scrollTo(newScrollOffset, duration: duration, curve: curve); return scrollTo(newScrollOffset, duration: duration, curve: curve);
} }
void fling(Offset velocity) { Future fling(Offset velocity) {
if (velocity != Offset.zero) { if (velocity != Offset.zero)
_startToEndAnimation(velocity: _scrollVelocity(velocity)); return _startToEndAnimation(velocity: _scrollVelocity(velocity));
} else if (!_toEndAnimation.isAnimating && (_toOffsetAnimation == null || !_toOffsetAnimation.isAnimating)) { if (!_toEndAnimation.isAnimating && (_toOffsetAnimation == null || !_toOffsetAnimation.isAnimating))
settleScrollOffset(); return settleScrollOffset();
} return new Future.value();
} }
void settleScrollOffset() { Future settleScrollOffset() {
_startToEndAnimation(); return _startToEndAnimation();
} }
double _scrollVelocity(sky.Offset velocity) { double _scrollVelocity(sky.Offset velocity) {
...@@ -623,12 +634,12 @@ class PageableListState<T> extends ScrollableListState<T, PageableList<T>> { ...@@ -623,12 +634,12 @@ class PageableListState<T> extends ScrollableListState<T, PageableList<T>> {
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset); .clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
} }
void fling(sky.Offset velocity) { Future fling(sky.Offset velocity) {
double scrollVelocity = _scrollVelocity(velocity); double scrollVelocity = _scrollVelocity(velocity);
double newScrollOffset = _snapScrollOffset(scrollOffset + scrollVelocity.sign * config.itemExtent) double newScrollOffset = _snapScrollOffset(scrollOffset + scrollVelocity.sign * config.itemExtent)
.clamp(_snapScrollOffset(scrollOffset - config.itemExtent / 2.0), .clamp(_snapScrollOffset(scrollOffset - config.itemExtent / 2.0),
_snapScrollOffset(scrollOffset + config.itemExtent / 2.0)); _snapScrollOffset(scrollOffset + config.itemExtent / 2.0));
scrollTo(newScrollOffset, duration: config.duration, curve: config.curve).then(_notifyPageChanged); return scrollTo(newScrollOffset, duration: config.duration, curve: config.curve).then(_notifyPageChanged);
} }
int get currentPage => (scrollOffset / config.itemExtent).floor() % itemCount; int get currentPage => (scrollOffset / config.itemExtent).floor() % itemCount;
...@@ -638,8 +649,8 @@ class PageableListState<T> extends ScrollableListState<T, PageableList<T>> { ...@@ -638,8 +649,8 @@ class PageableListState<T> extends ScrollableListState<T, PageableList<T>> {
config.onPageChanged(currentPage); config.onPageChanged(currentPage);
} }
void settleScrollOffset() { Future settleScrollOffset() {
scrollTo(_snapScrollOffset(scrollOffset), duration: config.duration, curve: config.curve).then(_notifyPageChanged); return scrollTo(_snapScrollOffset(scrollOffset), duration: config.duration, curve: config.curve).then(_notifyPageChanged);
} }
} }
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:quiver/testing/async.dart'; import 'package:quiver/testing/async.dart';
import 'package:sky/src/fn3.dart'; import 'package:sky/src/fn3.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
...@@ -49,11 +51,11 @@ void set scrollOffset(double value) { ...@@ -49,11 +51,11 @@ void set scrollOffset(double value) {
scrollableState.scrollTo(value); scrollableState.scrollTo(value);
} }
void fling(double velocity) { Future fling(double velocity) {
Offset velocityOffset = scrollDirection == ScrollDirection.vertical Offset velocityOffset = scrollDirection == ScrollDirection.vertical
? new Offset(0.0, velocity) ? new Offset(0.0, velocity)
: new Offset(velocity, 0.0); : new Offset(velocity, 0.0);
scrollableState.fling(velocityOffset); return scrollableState.fling(velocityOffset);
} }
void main() { void main() {
...@@ -66,7 +68,7 @@ void main() { ...@@ -66,7 +68,7 @@ void main() {
expect(scrollOffset, 0.0); expect(scrollOffset, 0.0);
double t0 = 0.0; double t0 = 0.0;
int dt = 1000; int dt = 2000;
new FakeAsync().run((async) { new FakeAsync().run((async) {
fling(-800.0); fling(-800.0);
tester.pumpFrameWithoutChange(t0); // Start the scheduler at 0.0 tester.pumpFrameWithoutChange(t0); // Start the scheduler at 0.0
...@@ -82,10 +84,10 @@ void main() { ...@@ -82,10 +84,10 @@ void main() {
expect(scrollOffset, 0.0); expect(scrollOffset, 0.0);
double t0 = 0.0; double t0 = 0.0;
int dt = 1000; int dt = 2000;
new FakeAsync().run((async) { new FakeAsync().run((async) {
fling(-2000.0); fling(-2000.0);
tester.pumpFrameWithoutChange(t0); // Start the scheduler at 0.0 tester.pumpFrameWithoutChange(t0);
tester.pumpFrameWithoutChange(t0 + dt); tester.pumpFrameWithoutChange(t0 + dt);
async.elapse(new Duration(milliseconds: dt)); async.elapse(new Duration(milliseconds: dt));
expect(scrollOffset, closeTo(400.0, 1.0)); expect(scrollOffset, closeTo(400.0, 1.0));
...@@ -94,14 +96,14 @@ void main() { ...@@ -94,14 +96,14 @@ void main() {
test('ScrollableList snap scrolling, fling(800)', () { test('ScrollableList snap scrolling, fling(800)', () {
scrollOffset = 400.0; scrollOffset = 400.0;
tester.pumpFrameWithoutChange(1000.0); tester.pumpFrameWithoutChange();
expect(scrollOffset, 400.0); expect(scrollOffset, 400.0);
double t0 = 0.0; double t0 = 0.0;
int dt = 2000; int dt = 2000;
new FakeAsync().run((async) { new FakeAsync().run((async) {
fling(800.0); fling(800.0);
tester.pumpFrameWithoutChange(t0); // Start the scheduler at 0.0 tester.pumpFrameWithoutChange(t0);
tester.pumpFrameWithoutChange(t0 + dt); tester.pumpFrameWithoutChange(t0 + dt);
async.elapse(new Duration(milliseconds: dt)); async.elapse(new Duration(milliseconds: dt));
expect(scrollOffset, closeTo(0.0, 1.0)); expect(scrollOffset, closeTo(0.0, 1.0));
...@@ -110,18 +112,38 @@ void main() { ...@@ -110,18 +112,38 @@ void main() {
test('ScrollableList snap scrolling, fling(2000)', () { test('ScrollableList snap scrolling, fling(2000)', () {
scrollOffset = 800.0; scrollOffset = 800.0;
tester.pumpFrameWithoutChange(1000.0); tester.pumpFrameWithoutChange();
expect(scrollOffset, 800.0); expect(scrollOffset, 800.0);
double t0 = 0.0; double t0 = 0.0;
int dt = 1500; int dt = 2000;
new FakeAsync().run((async) { new FakeAsync().run((async) {
fling(2000.0); fling(2000.0);
tester.pumpFrameWithoutChange(t0); // Start the scheduler at 0.0 tester.pumpFrameWithoutChange(t0);
tester.pumpFrameWithoutChange(t0 + dt); tester.pumpFrameWithoutChange(t0 + dt);
async.elapse(new Duration(milliseconds: dt)); async.elapse(new Duration(milliseconds: dt));
expect(scrollOffset, closeTo(200.0, 1.0)); expect(scrollOffset, closeTo(200.0, 1.0));
}); });
}); });
test('ScrollableList snap scrolling, fling(2000).then()', () {
scrollOffset = 800.0;
tester.pumpFrameWithoutChange();
expect(scrollOffset, 800.0);
double t0 = 0.0;
int dt = 2000;
bool completed = false;
new FakeAsync().run((async) {
fling(2000.0).then((_) {
completed = true;
expect(scrollOffset, closeTo(200.0, 1.0));
});
tester.pumpFrameWithoutChange(t0);
tester.pumpFrameWithoutChange(t0 + dt);
async.elapse(new Duration(milliseconds: dt));
expect(completed, true);
});
});
} }
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