Commit 9d1bb04a authored by Hans Muller's avatar Hans Muller

Merge pull request #974 from HansMuller/dismissable-uses-gestures

Convert Dismissable to use gestures

Convert Dismissable to use the ScrollStart, ScrollUpdate, and ScrollEnd gestures. Support for fling gestures is TBD.

Included a basic unit test that checks that one item can be dismissed with a press-drag-release gesture.

Fixed the scroll gesture recognizer: if the last pointer goes up and candidate recognizers still exist, then reject the gesture.
parents bd038cfc 7a42fe34
...@@ -76,6 +76,10 @@ abstract class _ScrollGestureRecognizer<T extends dynamic> extends GestureRecogn ...@@ -76,6 +76,10 @@ abstract class _ScrollGestureRecognizer<T extends dynamic> extends GestureRecogn
} }
void didStopTrackingLastPointer() { void didStopTrackingLastPointer() {
if (_state == ScrollState.possible) {
resolve(GestureDisposition.rejected);
return;
}
bool wasAccepted = (_state == ScrollState.accepted); bool wasAccepted = (_state == ScrollState.accepted);
_state = ScrollState.ready; _state = ScrollState.ready;
if (wasAccepted && onEnd != null) if (wasAccepted && onEnd != null)
......
...@@ -10,6 +10,7 @@ import 'package:sky/animation/curves.dart'; ...@@ -10,6 +10,7 @@ import 'package:sky/animation/curves.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/transitions.dart'; import 'package:sky/widgets/transitions.dart';
import 'package:sky/widgets/framework.dart'; import 'package:sky/widgets/framework.dart';
import 'package:sky/widgets/gesture_detector.dart';
const Duration _kCardDismissFadeout = const Duration(milliseconds: 200); const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
const Duration _kCardDismissResize = const Duration(milliseconds: 300); const Duration _kCardDismissResize = const Duration(milliseconds: 300);
...@@ -99,45 +100,33 @@ class Dismissable extends StatefulComponent { ...@@ -99,45 +100,33 @@ class Dismissable extends StatefulComponent {
_maybeCallOnResized(); _maybeCallOnResized();
} }
EventDisposition _handlePointerDown(sky.PointerEvent event) { void _handleScrollStart() {
if (_fadePerformance.isAnimating) if (_fadePerformance.isAnimating)
return EventDisposition.processed; return;
_dragUnderway = true; _dragUnderway = true;
_dragX = 0.0; _dragX = 0.0;
_fadePerformance.progress = 0.0; _fadePerformance.progress = 0.0;
return EventDisposition.processed;
} }
EventDisposition _handlePointerMove(sky.PointerEvent event) { void _handleScrollUpdate(double scrollOffset) {
if (!_isActive) if (!_isActive || _fadePerformance.isAnimating)
return EventDisposition.ignored; return;
if (_fadePerformance.isAnimating)
return EventDisposition.processed;
double oldDragX = _dragX; double oldDragX = _dragX;
_dragX += event.dx; _dragX -= scrollOffset;
if (oldDragX.sign != _dragX.sign) if (oldDragX.sign != _dragX.sign)
setState(() {}); // Rebuild to update the new drag endpoint. setState(() {}); // Rebuild to update the new drag endpoint.
if (!_fadePerformance.isAnimating) if (!_fadePerformance.isAnimating)
_fadePerformance.progress = _dragX.abs() / (_size.width * _kDismissCardThreshold); _fadePerformance.progress = _dragX.abs() / (_size.width * _kDismissCardThreshold);
return EventDisposition.processed;
} }
EventDisposition _handlePointerUpOrCancel(_) { _handleScrollEnd() {
if (!_isActive) if (!_isActive || _fadePerformance.isAnimating)
return EventDisposition.ignored; return;
if (_fadePerformance.isAnimating)
return EventDisposition.processed;
_dragUnderway = false; _dragUnderway = false;
if (_fadePerformance.isCompleted) if (_fadePerformance.isCompleted)
_startResizePerformance(); _startResizePerformance();
else if (!_fadePerformance.isAnimating) else if (!_fadePerformance.isAnimating)
_fadePerformance.reverse(); _fadePerformance.reverse();
return EventDisposition.processed;
} }
bool _isHorizontalFlingGesture(sky.GestureEvent event) { bool _isHorizontalFlingGesture(sky.GestureEvent event) {
...@@ -185,22 +174,23 @@ class Dismissable extends StatefulComponent { ...@@ -185,22 +174,23 @@ class Dismissable extends StatefulComponent {
height: dismissHeight); height: dismissHeight);
} }
return new Listener( return new GestureDetector(
onPointerDown: _handlePointerDown, onHorizontalScrollStart: _handleScrollStart,
onPointerMove: _handlePointerMove, onHorizontalScrollUpdate: _handleScrollUpdate,
onPointerUp: _handlePointerUpOrCancel, onHorizontalScrollEnd: _handleScrollEnd,
onPointerCancel: _handlePointerUpOrCancel, child: new Listener(
onGestureFlingStart: _handleFlingStart, onGestureFlingStart: _handleFlingStart,
child: new SizeObserver( child: new SizeObserver(
callback: _handleSizeChanged, callback: _handleSizeChanged,
child: new FadeTransition( child: new FadeTransition(
performance: _fadePerformance,
onCompleted: _handleFadeCompleted,
opacity: new AnimatedValue<double>(1.0, end: 0.0),
child: new SlideTransition(
performance: _fadePerformance, performance: _fadePerformance,
position: new AnimatedValue<Point>(Point.origin, end: _activeCardDragEndPoint), onCompleted: _handleFadeCompleted,
child: child opacity: new AnimatedValue<double>(1.0, end: 0.0),
child: new SlideTransition(
performance: _fadePerformance,
position: new AnimatedValue<Point>(Point.origin, end: _activeCardDragEndPoint),
child: child
)
) )
) )
) )
......
import 'package:quiver/testing/async.dart';
import 'package:sky/widgets.dart';
import 'package:test/test.dart';
import '../engine/mock_events.dart';
import 'widget_tester.dart';
void main() {
test('Horizontal drag triggers dismiss', () {
WidgetTester tester = new WidgetTester();
TestPointer pointer = new TestPointer(5);
const double itemHeight = 50.0;
List<int> dismissedItems = [];
void handleOnResized(item) {
expect(dismissedItems.contains(item), isFalse);
}
void handleOnDismissed(item) {
expect(dismissedItems.contains(item), isFalse);
dismissedItems.add(item);
}
Widget buildDismissableItem(int item) {
return new Dismissable(
key: new ValueKey<int>(item),
onDismissed: () { handleOnDismissed(item); },
onResized: () { handleOnResized(item); },
child: new Container(
height: itemHeight,
child: new Text(item.toString())
)
);
}
Widget builder() {
return new Container(
padding: const EdgeDims.all(10.0),
child: new ScrollableList<int>(
items: [0, 1, 2, 3, 4, 5],
itemBuilder: buildDismissableItem,
scrollDirection: ScrollDirection.vertical,
itemExtent: itemHeight
)
);
}
tester.pumpFrame(builder);
Widget item3 = tester.findText("3");
expect(item3, isNotNull);
expect(dismissedItems, isEmpty);
// Gesture: press-drag-release from the Dismissable's top-left corner
// to its top-right corner. Triggers the resize animation which concludes
// by calling onDismissed().
Point downLocation = tester.getTopLeft(item3);
Point upLocation = tester.getTopRight(item3);
tester.dispatchEvent(pointer.down(downLocation), downLocation);
tester.dispatchEvent(pointer.move(upLocation), upLocation);
tester.dispatchEvent(pointer.up(), upLocation);
new FakeAsync().run((async) {
tester.pumpFrame(builder); // start the resize animation
tester.pumpFrame(builder, 1000.0); // finish the resize animation
async.elapse(new Duration(seconds: 1));
tester.pumpFrame(builder, 2000.0); // dismiss
async.elapse(new Duration(seconds: 1));
expect(dismissedItems, equals([3]));
expect(tester.findText("3"), isNull);
});
});
}
...@@ -83,6 +83,20 @@ class WidgetTester { ...@@ -83,6 +83,20 @@ class WidgetTester {
return box.localToGlobal(box.size.center(Point.origin)); return box.localToGlobal(box.size.center(Point.origin));
} }
Point getTopLeft(Widget widget) {
assert(widget != null);
RenderBox box = widget.renderObject as RenderBox;
assert(box != null);
return box.localToGlobal(Point.origin);
}
Point getTopRight(Widget widget) {
assert(widget != null);
RenderBox box = widget.renderObject as RenderBox;
assert(box != null);
return box.localToGlobal(box.size.topRight(Point.origin));
}
HitTestResult _hitTest(Point location) => SkyBinding.instance.hitTest(location); HitTestResult _hitTest(Point location) => SkyBinding.instance.hitTest(location);
EventDisposition _dispatchEvent(sky.Event event, HitTestResult result) { EventDisposition _dispatchEvent(sky.Event event, HitTestResult result) {
......
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