From b7f6be6ff29f2d8dec9f59a13cfc84576acdbe3d Mon Sep 17 00:00:00 2001 From: Tom Larsen Date: Tue, 30 Jan 2018 01:48:35 -0500 Subject: [PATCH] Add onLeave callback to DragTarget (#14103) * Add a callback that fires when a Draggable leaves a DragTarget. This enables the DragTarget to manage its state from entry to exit. * It helps to have a null check here * Add test for onLeave callback and add verbiage to onWillAccept explaining the callback lifecycle of a DragTarget. --- .../flutter/lib/src/widgets/drag_target.dart | 18 ++++- .../flutter/test/widgets/draggable_test.dart | 67 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 8f71dc65e5..6fe4b02517 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -37,6 +37,11 @@ typedef Widget DragTargetBuilder(BuildContext context, List candidateData, /// Used by [Draggable.onDraggableCanceled]. typedef void DraggableCanceledCallback(Velocity velocity, Offset offset); +/// Signature for when a [Draggable] leaves a [DragTarget]. +/// +/// Used by [DragTarget.onLeave]. +typedef void DragTargetLeave(T data); + /// Where the [Draggable] should be anchored during a drag. enum DragAnchor { /// Display the feedback anchored at the position of the original child. If @@ -372,7 +377,8 @@ class DragTarget extends StatefulWidget { Key key, @required this.builder, this.onWillAccept, - this.onAccept + this.onAccept, + this.onLeave, }) : super(key: key); /// Called to build the contents of this widget. @@ -383,11 +389,19 @@ class DragTarget extends StatefulWidget { /// Called to determine whether this widget is interested in receiving a given /// piece of data being dragged over this drag target. + /// + /// Called when a piece of data enters the target. This will be followed by + /// either [onAccept], if the data is dropped, or [onLeave], if the drag + /// leaves the target. final DragTargetWillAccept onWillAccept; /// Called when an acceptable piece of data was dropped over this drag target. final DragTargetAccept onAccept; + /// Called when a given piece of data being dragged over this target leaves + /// the target. + final DragTargetLeave onLeave; + @override _DragTargetState createState() => new _DragTargetState(); } @@ -421,6 +435,8 @@ class _DragTargetState extends State> { _candidateAvatars.remove(avatar); _rejectedAvatars.remove(avatar); }); + if (widget.onLeave != null) + widget.onLeave(avatar.data); } void didDrop(_DragAvatar avatar) { diff --git a/packages/flutter/test/widgets/draggable_test.dart b/packages/flutter/test/widgets/draggable_test.dart index cf817dcd0b..0c68d1ccb9 100644 --- a/packages/flutter/test/widgets/draggable_test.dart +++ b/packages/flutter/test/widgets/draggable_test.dart @@ -68,6 +68,73 @@ void main() { expect(dragStartedCount, 1); }); + testWidgets('Drag and drop - onLeave callback fires correctly', (WidgetTester tester) async { + final Map leftBehind = { + 'Target 1': 0, + 'Target 2': 0, + }; + + await tester.pumpWidget(new MaterialApp( + home: new Column( + children: [ + const Draggable( + data: 1, + child: const Text('Source'), + feedback: const Text('Dragging'), + ), + new DragTarget( + builder: (BuildContext context, List data, List rejects) { + return new Container(height: 100.0, child: const Text('Target 1')); + }, + onLeave: (int data) => leftBehind['Target 1'] = leftBehind['Target 1'] + data, + ), + new DragTarget( + builder: (BuildContext context, List data, List rejects) { + return new Container(height: 100.0, child: const Text('Target 2')); + }, + onLeave: (int data) => leftBehind['Target 2'] = leftBehind['Target 2'] + data, + ), + ], + ), + )); + + expect(leftBehind['Target 1'], equals(0)); + expect(leftBehind['Target 2'], equals(0)); + + final Offset firstLocation = tester.getCenter(find.text('Source')); + final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(0)); + expect(leftBehind['Target 2'], equals(0)); + + final Offset secondLocation = tester.getCenter(find.text('Target 1')); + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(0)); + expect(leftBehind['Target 2'], equals(0)); + + final Offset thirdLocation = tester.getCenter(find.text('Target 2')); + await gesture.moveTo(thirdLocation); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(1)); + expect(leftBehind['Target 2'], equals(0)); + + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(1)); + expect(leftBehind['Target 2'], equals(1)); + + await gesture.up(); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(1)); + expect(leftBehind['Target 2'], equals(1)); + }); + testWidgets('Drag and drop - dragging over button', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation;