Commit 7a42fe34 authored by Hans Muller's avatar Hans Muller

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.
parent 17476a68
......@@ -76,6 +76,10 @@ abstract class _ScrollGestureRecognizer<T extends dynamic> extends GestureRecogn
}
void didStopTrackingLastPointer() {
if (_state == ScrollState.possible) {
resolve(GestureDisposition.rejected);
return;
}
bool wasAccepted = (_state == ScrollState.accepted);
_state = ScrollState.ready;
if (wasAccepted && onEnd != null)
......
......@@ -10,6 +10,7 @@ import 'package:sky/animation/curves.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/transitions.dart';
import 'package:sky/widgets/framework.dart';
import 'package:sky/widgets/gesture_detector.dart';
const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
const Duration _kCardDismissResize = const Duration(milliseconds: 300);
......@@ -99,45 +100,33 @@ class Dismissable extends StatefulComponent {
_maybeCallOnResized();
}
EventDisposition _handlePointerDown(sky.PointerEvent event) {
void _handleScrollStart() {
if (_fadePerformance.isAnimating)
return EventDisposition.processed;
return;
_dragUnderway = true;
_dragX = 0.0;
_fadePerformance.progress = 0.0;
return EventDisposition.processed;
}
EventDisposition _handlePointerMove(sky.PointerEvent event) {
if (!_isActive)
return EventDisposition.ignored;
if (_fadePerformance.isAnimating)
return EventDisposition.processed;
void _handleScrollUpdate(double scrollOffset) {
if (!_isActive || _fadePerformance.isAnimating)
return;
double oldDragX = _dragX;
_dragX += event.dx;
_dragX -= scrollOffset;
if (oldDragX.sign != _dragX.sign)
setState(() {}); // Rebuild to update the new drag endpoint.
if (!_fadePerformance.isAnimating)
_fadePerformance.progress = _dragX.abs() / (_size.width * _kDismissCardThreshold);
return EventDisposition.processed;
}
EventDisposition _handlePointerUpOrCancel(_) {
if (!_isActive)
return EventDisposition.ignored;
if (_fadePerformance.isAnimating)
return EventDisposition.processed;
_handleScrollEnd() {
if (!_isActive || _fadePerformance.isAnimating)
return;
_dragUnderway = false;
if (_fadePerformance.isCompleted)
_startResizePerformance();
else if (!_fadePerformance.isAnimating)
_fadePerformance.reverse();
return EventDisposition.processed;
}
bool _isHorizontalFlingGesture(sky.GestureEvent event) {
......@@ -185,22 +174,23 @@ class Dismissable extends StatefulComponent {
height: dismissHeight);
}
return new Listener(
onPointerDown: _handlePointerDown,
onPointerMove: _handlePointerMove,
onPointerUp: _handlePointerUpOrCancel,
onPointerCancel: _handlePointerUpOrCancel,
onGestureFlingStart: _handleFlingStart,
child: new SizeObserver(
callback: _handleSizeChanged,
child: new FadeTransition(
performance: _fadePerformance,
onCompleted: _handleFadeCompleted,
opacity: new AnimatedValue<double>(1.0, end: 0.0),
child: new SlideTransition(
return new GestureDetector(
onHorizontalScrollStart: _handleScrollStart,
onHorizontalScrollUpdate: _handleScrollUpdate,
onHorizontalScrollEnd: _handleScrollEnd,
child: new Listener(
onGestureFlingStart: _handleFlingStart,
child: new SizeObserver(
callback: _handleSizeChanged,
child: new FadeTransition(
performance: _fadePerformance,
position: new AnimatedValue<Point>(Point.origin, end: _activeCardDragEndPoint),
child: child
onCompleted: _handleFadeCompleted,
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 {
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);
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