diff --git a/packages/flutter/lib/src/gestures/drag.dart b/packages/flutter/lib/src/gestures/drag.dart index 0a75630e19..c3cba6cb56 100644 --- a/packages/flutter/lib/src/gestures/drag.dart +++ b/packages/flutter/lib/src/gestures/drag.dart @@ -14,13 +14,17 @@ enum DragState { accepted } +typedef void GestureDragDownCallback(Point globalPosition); typedef void GestureDragStartCallback(Point globalPosition); typedef void GestureDragUpdateCallback(double delta); typedef void GestureDragEndCallback(Velocity velocity); +typedef void GestureDragCancelCallback(); +typedef void GesturePanDownCallback(Point globalPosition); typedef void GesturePanStartCallback(Point globalPosition); typedef void GesturePanUpdateCallback(Offset delta); typedef void GesturePanEndCallback(Velocity velocity); +typedef void GesturePanCancelCallback(); typedef void _GesturePolymorphicUpdateCallback(T delta); @@ -32,9 +36,11 @@ bool _isFlingGesture(Velocity velocity) { } abstract class _DragGestureRecognizer extends OneSequenceGestureRecognizer { + GestureDragDownCallback onDown; GestureDragStartCallback onStart; _GesturePolymorphicUpdateCallback onUpdate; GestureDragEndCallback onEnd; + GestureDragCancelCallback onCancel; DragState _state = DragState.ready; Point _initialPosition; @@ -54,6 +60,8 @@ abstract class _DragGestureRecognizer extends OneSequenceGest _state = DragState.possible; _initialPosition = event.position; _pendingDragDelta = _initialPendingDragDelta; + if (onDown != null) + onDown(_initialPosition); } } @@ -90,11 +98,18 @@ abstract class _DragGestureRecognizer extends OneSequenceGest } } + @override + void rejectGesture(int pointer) { + ensureNotTrackingPointer(pointer); + } + @override void didStopTrackingLastPointer(int pointer) { if (_state == DragState.possible) { resolve(GestureDisposition.rejected); _state = DragState.ready; + if (onCancel != null) + onCancel(); return; } bool wasAccepted = (_state == DragState.accepted); diff --git a/packages/flutter/lib/src/gestures/recognizer.dart b/packages/flutter/lib/src/gestures/recognizer.dart index 9db54cbff9..b3aec99373 100644 --- a/packages/flutter/lib/src/gestures/recognizer.dart +++ b/packages/flutter/lib/src/gestures/recognizer.dart @@ -99,6 +99,11 @@ abstract class OneSequenceGestureRecognizer extends GestureRecognizer { didStopTrackingLastPointer(pointer); } + void ensureNotTrackingPointer(int pointer) { + if (_trackedPointers.contains(pointer)) + stopTrackingPointer(pointer); + } + void stopTrackingIfPointerNoLongerDown(PointerEvent event) { if (event is PointerUpEvent || event is PointerCancelEvent) stopTrackingPointer(event.pointer); diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index 201a640e7f..302dde33ec 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -122,11 +122,21 @@ class DrawerControllerState extends State { AnimationController _controller; - void _handleTapDown(Point position) { + void _handleDragDown(Point position) { _controller.stop(); _ensureHistoryEntry(); } + void _handleDragCancel() { + if (_controller.isDismissed || _controller.isAnimating) + return; + if (_controller.value < 0.5) { + close(); + } else { + open(); + } + } + double get _width { assert(!Scheduler.debugInFrame); // we should never try to read the tree state while building or laying out RenderBox drawerBox = _drawerKey.currentContext?.findRenderObject(); @@ -179,8 +189,10 @@ class DrawerControllerState extends State { } else { return new GestureDetector( key: _gestureDetectorKey, + onHorizontalDragDown: _handleDragDown, onHorizontalDragUpdate: _move, onHorizontalDragEnd: _settle, + onHorizontalDragCancel: _handleDragCancel, child: new RepaintBoundary( child: new Stack( children: [ @@ -195,16 +207,13 @@ class DrawerControllerState extends State { ), new Align( alignment: const FractionalOffset(0.0, 0.5), - child: new GestureDetector( - onTapDown: _handleTapDown, - child: new Align( - alignment: const FractionalOffset(1.0, 0.5), - widthFactor: _controller.value, - child: new RepaintBoundary( - child: new Focus( - key: _drawerKey, - child: config.child - ) + child: new Align( + alignment: const FractionalOffset(1.0, 0.5), + widthFactor: _controller.value, + child: new RepaintBoundary( + child: new Focus( + key: _drawerKey, + child: config.child ) ) ) diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index cd0fde5bc2..2a6bce027a 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -14,15 +14,16 @@ export 'package:flutter/gestures.dart' show GestureTapCallback, GestureTapCancelCallback, GestureLongPressCallback, + GestureDragDownCallback, GestureDragStartCallback, GestureDragUpdateCallback, GestureDragEndCallback, - GestureDragStartCallback, - GestureDragUpdateCallback, - GestureDragEndCallback, + GestureDragCancelCallback, + GesturePanDownCallback, GesturePanStartCallback, GesturePanUpdateCallback, GesturePanEndCallback, + GesturePanCancelCallback, GestureScaleStartCallback, GestureScaleUpdateCallback, GestureScaleEndCallback, @@ -49,15 +50,21 @@ class GestureDetector extends StatelessWidget { this.onTapCancel, this.onDoubleTap, this.onLongPress, + this.onVerticalDragDown, this.onVerticalDragStart, this.onVerticalDragUpdate, this.onVerticalDragEnd, + this.onVerticalDragCancel, + this.onHorizontalDragDown, this.onHorizontalDragStart, this.onHorizontalDragUpdate, this.onHorizontalDragEnd, + this.onHorizontalDragCancel, + this.onPanDown, this.onPanStart, this.onPanUpdate, this.onPanEnd, + this.onPanCancel, this.onScaleStart, this.onScaleUpdate, this.onScaleEnd, @@ -116,6 +123,9 @@ class GestureDetector extends StatelessWidget { final GestureLongPressCallback onLongPress; /// A pointer has contacted the screen and might begin to move vertically. + final GestureDragDownCallback onVerticalDragDown; + + /// A pointer has contacted the screen and has begun to move vertically. final GestureDragStartCallback onVerticalDragStart; /// A pointer that is in contact with the screen and moving vertically has @@ -127,7 +137,14 @@ class GestureDetector extends StatelessWidget { /// specific velocity when it stopped contacting the screen. final GestureDragEndCallback onVerticalDragEnd; + /// The pointer that previously triggered the [onVerticalDragDown] did not + /// end up moving vertically. + final GestureDragCancelCallback onVerticalDragCancel; + /// A pointer has contacted the screen and might begin to move horizontally. + final GestureDragDownCallback onHorizontalDragDown; + + /// A pointer has contacted the screen and has begun to move horizontally. final GestureDragStartCallback onHorizontalDragStart; /// A pointer that is in contact with the screen and moving horizontally has @@ -139,9 +156,15 @@ class GestureDetector extends StatelessWidget { /// specific velocity when it stopped contacting the screen. final GestureDragEndCallback onHorizontalDragEnd; + /// The pointer that previously triggered the [onHorizontalDragDown] did not + /// end up moving horizontally. + final GestureDragCancelCallback onHorizontalDragCancel; + + final GesturePanDownCallback onPanDown; final GesturePanStartCallback onPanStart; final GesturePanUpdateCallback onPanUpdate; final GesturePanEndCallback onPanEnd; + final GesturePanCancelCallback onPanCancel; final GestureScaleStartCallback onScaleStart; final GestureScaleUpdateCallback onScaleUpdate; @@ -185,30 +208,48 @@ class GestureDetector extends StatelessWidget { }; } - if (onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null) { + if (onVerticalDragDown != null || + onVerticalDragStart != null || + onVerticalDragUpdate != null || + onVerticalDragEnd != null || + onVerticalDragCancel != null) { gestures[VerticalDragGestureRecognizer] = (VerticalDragGestureRecognizer recognizer) { return (recognizer ??= new VerticalDragGestureRecognizer()) + ..onDown = onVerticalDragDown ..onStart = onVerticalDragStart ..onUpdate = onVerticalDragUpdate - ..onEnd = onVerticalDragEnd; + ..onEnd = onVerticalDragEnd + ..onCancel = onVerticalDragCancel; }; } - if (onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null) { + if (onHorizontalDragDown != null || + onHorizontalDragStart != null || + onHorizontalDragUpdate != null || + onHorizontalDragEnd != null || + onHorizontalDragCancel != null) { gestures[HorizontalDragGestureRecognizer] = (HorizontalDragGestureRecognizer recognizer) { return (recognizer ??= new HorizontalDragGestureRecognizer()) + ..onDown = onHorizontalDragDown ..onStart = onHorizontalDragStart ..onUpdate = onHorizontalDragUpdate - ..onEnd = onHorizontalDragEnd; + ..onEnd = onHorizontalDragEnd + ..onCancel = onHorizontalDragCancel; }; } - if (onPanStart != null || onPanUpdate != null || onPanEnd != null) { + if (onPanDown != null || + onPanStart != null || + onPanUpdate != null || + onPanEnd != null || + onPanCancel != null) { gestures[PanGestureRecognizer] = (PanGestureRecognizer recognizer) { return (recognizer ??= new PanGestureRecognizer()) + ..onDown = onPanDown ..onStart = onPanStart ..onUpdate = onPanUpdate - ..onEnd = onPanEnd; + ..onEnd = onPanEnd + ..onCancel = onPanCancel; }; } diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 4f606ff650..51390f9371 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -470,7 +470,7 @@ abstract class ScrollableState extends State { config.onScroll(_scrollOffset); } - void _handlePointerDown(_) { + void _handleDragDown(_) { _controller.stop(); } @@ -527,10 +527,7 @@ abstract class ScrollableState extends State { key: _gestureDetectorKey, gestures: buildGestureDetectors(), behavior: HitTestBehavior.opaque, - child: new Listener( - child: buildContent(context), - onPointerDown: _handlePointerDown - ) + child: buildContent(context) ); } @@ -559,6 +556,7 @@ abstract class ScrollableState extends State { return { VerticalDragGestureRecognizer: (VerticalDragGestureRecognizer recognizer) { return (recognizer ??= new VerticalDragGestureRecognizer()) + ..onDown = _handleDragDown ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate ..onEnd = _handleDragEnd; @@ -568,6 +566,7 @@ abstract class ScrollableState extends State { return { HorizontalDragGestureRecognizer: (HorizontalDragGestureRecognizer recognizer) { return (recognizer ??= new HorizontalDragGestureRecognizer()) + ..onDown = _handleDragDown ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate ..onEnd = _handleDragEnd; diff --git a/packages/flutter/test/widget/drawer_test.dart b/packages/flutter/test/widget/drawer_test.dart index 3f8f5ad887..ed55448716 100644 --- a/packages/flutter/test/widget/drawer_test.dart +++ b/packages/flutter/test/widget/drawer_test.dart @@ -45,7 +45,6 @@ void main() { test('Drawer tap test', () { testWidgets((WidgetTester tester) { GlobalKey scaffoldKey = new GlobalKey(); - tester.pumpWidget(new Container()); // throw away the old App and its Navigator tester.pumpWidget( new MaterialApp( routes: { @@ -81,4 +80,63 @@ void main() { }); }); + test('Drawer drag cancel resume', () { + testWidgets((WidgetTester tester) { + GlobalKey scaffoldKey = new GlobalKey(); + tester.pumpWidget( + new MaterialApp( + routes: { + '/': (BuildContext context) { + return new Scaffold( + key: scaffoldKey, + drawer: new Drawer( + child: new Block( + children: [ + new Text('drawer'), + new Container( + height: 1000.0, + decoration: new BoxDecoration( + backgroundColor: Colors.blue[500] + ) + ), + ] + ) + ), + body: new Container() + ); + } + } + ) + ); + expect(tester.findText('drawer'), isNull); + scaffoldKey.currentState.openDrawer(); + tester.pump(); // drawer should be starting to animate in + expect(tester.findText('drawer'), isNotNull); + tester.pump(new Duration(seconds: 1)); // animation done + expect(tester.findText('drawer'), isNotNull); + + tester.tapAt(const Point(750.0, 100.0)); // on the mask + tester.pump(); + tester.pump(new Duration(milliseconds: 10)); + // drawer should be starting to animate away + RenderBox textBox = tester.findText('drawer').renderObject; + double textLeft = textBox.localToGlobal(Point.origin).x; + expect(textLeft, lessThan(0.0)); + + TestGesture gesture = tester.startGesture(new Point(100.0, 100.0)); + // drawer should be stopped. + tester.pump(); + tester.pump(new Duration(milliseconds: 10)); + expect(textBox.localToGlobal(Point.origin).x, equals(textLeft)); + + gesture.moveBy(new Offset(0.0, -50.0)); + // drawer should be returning to visible + tester.pump(); + tester.pump(new Duration(seconds: 1)); + expect(textBox.localToGlobal(Point.origin).x, equals(0.0)); + + gesture.up(); + }); + }); + }