diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 74fd565ceb..eaba8544df 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -524,9 +524,12 @@ class _DragTargetState extends State> { _candidateAvatars.add(avatar); }); return true; + } else { + setState(() { + _rejectedAvatars.add(avatar); + }); + return false; } - _rejectedAvatars.add(avatar); - return false; } void didLeave(_DragAvatar avatar) { diff --git a/packages/flutter/test/widgets/draggable_test.dart b/packages/flutter/test/widgets/draggable_test.dart index 5377cec38a..39e0b5a192 100644 --- a/packages/flutter/test/widgets/draggable_test.dart +++ b/packages/flutter/test/widgets/draggable_test.dart @@ -972,6 +972,144 @@ void main() { Offset(secondLocation.dx, secondLocation.dy - firstLocation.dy))); }); + testWidgets('Drag and drop - DragTarget rebuilds with and without rejected data when a rejected draggable enters and leaves', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Column( + children: [ + const Draggable( + data: 1, + child: Text('Source'), + feedback: Text('Dragging'), + ), + DragTarget( + builder: + (BuildContext context, List data, List rejects) { + return Container( + height: 100.0, + child: rejects.isNotEmpty + ? const Text('Rejected') + : const Text('Target'), + ); + }, + onWillAccept: (int data) => false, + ), + ], + ), + )); + + expect(find.text('Dragging'), findsNothing); + expect(find.text('Target'), findsOneWidget); + expect(find.text('Rejected'), findsNothing); + + final Offset firstLocation = tester.getTopLeft(find.text('Source')); + final TestGesture gesture = + await tester.startGesture(firstLocation, pointer: 7); + await tester.pump(); + + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsOneWidget); + expect(find.text('Rejected'), findsNothing); + + final Offset secondLocation = tester.getCenter(find.text('Target')); + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsNothing); + expect(find.text('Rejected'), findsOneWidget); + + await gesture.moveTo(firstLocation); + await tester.pump(); + + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsOneWidget); + expect(find.text('Rejected'), findsNothing); + }); + + + testWidgets('Drag and drop - Can drag and drop over a non-accepting target multiple times', (WidgetTester tester) async { + int numberOfTimesOnDraggableCanceledCalled = 0; + await tester.pumpWidget(MaterialApp( + home: Column( + children: [ + Draggable( + data: 1, + child: const Text('Source'), + feedback: const Text('Dragging'), + onDraggableCanceled: (Velocity velocity, Offset offset) { + numberOfTimesOnDraggableCanceledCalled++; + }, + ), + DragTarget( + builder: + (BuildContext context, List data, List rejects) { + return Container( + height: 100.0, + child: rejects.isNotEmpty + ? const Text('Rejected') + : const Text('Target'), + ); + }, + onWillAccept: (int data) => false, + ), + ], + ), + )); + + expect(find.text('Dragging'), findsNothing); + expect(find.text('Target'), findsOneWidget); + expect(find.text('Rejected'), findsNothing); + + final Offset firstLocation = tester.getTopLeft(find.text('Source')); + final TestGesture gesture = + await tester.startGesture(firstLocation, pointer: 7); + await tester.pump(); + + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsOneWidget); + expect(find.text('Rejected'), findsNothing); + + final Offset secondLocation = tester.getCenter(find.text('Target')); + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsNothing); + expect(find.text('Rejected'), findsOneWidget); + + await gesture.up(); + await tester.pump(); + + expect(find.text('Dragging'), findsNothing); + expect(find.text('Target'), findsOneWidget); + expect(find.text('Rejected'), findsNothing); + expect(numberOfTimesOnDraggableCanceledCalled, 1); + + // Drag and drop the Draggable onto the Target a second time. + final TestGesture secondGesture = + await tester.startGesture(firstLocation, pointer: 7); + await tester.pump(); + + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsOneWidget); + expect(find.text('Rejected'), findsNothing); + + await secondGesture.moveTo(secondLocation); + await tester.pump(); + + expect(find.text('Dragging'), findsOneWidget); + expect(find.text('Target'), findsNothing); + expect(find.text('Rejected'), findsOneWidget); + + await secondGesture.up(); + await tester.pump(); + + expect(numberOfTimesOnDraggableCanceledCalled, 2); + expect(find.text('Dragging'), findsNothing); + expect(find.text('Target'), findsOneWidget); + expect(find.text('Rejected'), findsNothing); + }); + testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target', (WidgetTester tester) async { final List accepted = []; bool onDragCompletedCalled = false;