From 0fdb21f31f39637cebd7d90cc5e22630088b791a Mon Sep 17 00:00:00 2001 From: Daniel Iglesia Date: Tue, 28 Jul 2020 09:56:09 -0700 Subject: [PATCH] Adds an onMove callback on a DragTarget. (#60174) --- .../flutter/lib/src/widgets/drag_target.dart | 31 ++++++- .../flutter/test/widgets/draggable_test.dart | 83 +++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index fc64baf9da..67f65f1bd9 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -57,6 +57,11 @@ typedef DragEndCallback = void Function(DraggableDetails details); /// Used by [DragTarget.onLeave]. typedef DragTargetLeave = void Function(Object data); +/// Signature for when a [Draggable] moves within a [DragTarget]. +/// +/// Used by [DragTarget.onMove]. +typedef DragTargetMove = void Function(DragTargetDetails details); + /// Where the [Draggable] should be anchored during a drag. enum DragAnchor { /// Display the feedback anchored at the position of the original child. If @@ -504,6 +509,7 @@ class DragTarget extends StatefulWidget { this.onAccept, this.onAcceptWithDetails, this.onLeave, + this.onMove, }) : super(key: key); /// Called to build the contents of this widget. @@ -535,6 +541,11 @@ class DragTarget extends StatefulWidget { /// the target. final DragTargetLeave onLeave; + /// Called when a [Draggable] moves within this [DragTarget]. + /// + /// Note that this includes entering and leaving the target. + final DragTargetMove onMove; + @override _DragTargetState createState() => _DragTargetState(); } @@ -588,6 +599,13 @@ class _DragTargetState extends State> { widget.onAcceptWithDetails(DragTargetDetails(data: avatar.data as T, offset: avatar._lastOffset)); } + void didMove(_DragAvatar avatar) { + if (!mounted) + return; + if (widget.onMove != null) + widget.onMove(DragTargetDetails(data: avatar.data, offset: avatar._lastOffset)); + } + @override Widget build(BuildContext context) { assert(widget.builder != null); @@ -680,9 +698,13 @@ class _DragAvatar extends Drag { } } - // If everything's the same, bail early. - if (listsMatch) + // If everything's the same, report moves, and bail early. + if (listsMatch) { + for (final _DragTargetState target in _enteredTargets) { + target.didMove(this); + } return; + } // Leave old targets. _leaveAllEntered(); @@ -696,6 +718,11 @@ class _DragAvatar extends Drag { orElse: () => null, ); + // Report moves to the targets. + for (final _DragTargetState target in _enteredTargets) { + target.didMove(this); + } + _activeTarget = newTarget; } diff --git a/packages/flutter/test/widgets/draggable_test.dart b/packages/flutter/test/widgets/draggable_test.dart index 410ea6ea62..ab84c3872d 100644 --- a/packages/flutter/test/widgets/draggable_test.dart +++ b/packages/flutter/test/widgets/draggable_test.dart @@ -17,6 +17,7 @@ void main() { final List accepted = []; final List> acceptedDetails = >[]; int dragStartedCount = 0; + int moveCount = 0; await tester.pumpWidget(MaterialApp( home: Column( @@ -33,6 +34,7 @@ void main() { builder: (BuildContext context, List data, List rejects) { return Container(height: 100.0, child: const Text('Target')); }, + onMove: (_) => moveCount++, onAccept: accepted.add, onAcceptWithDetails: acceptedDetails.add, ), @@ -46,6 +48,7 @@ void main() { expect(find.text('Dragging'), findsNothing); expect(find.text('Target'), findsOneWidget); expect(dragStartedCount, 0); + expect(moveCount, 0); final Offset firstLocation = tester.getCenter(find.text('Source')); final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7); @@ -57,6 +60,7 @@ void main() { expect(find.text('Dragging'), findsOneWidget); expect(find.text('Target'), findsOneWidget); expect(dragStartedCount, 1); + expect(moveCount, 0); final Offset secondLocation = tester.getCenter(find.text('Target')); await gesture.moveTo(secondLocation); @@ -68,6 +72,7 @@ void main() { expect(find.text('Dragging'), findsOneWidget); expect(find.text('Target'), findsOneWidget); expect(dragStartedCount, 1); + expect(moveCount, 1); await gesture.up(); await tester.pump(); @@ -79,6 +84,7 @@ void main() { expect(find.text('Dragging'), findsNothing); expect(find.text('Target'), findsOneWidget); expect(dragStartedCount, 1); + expect(moveCount, 1); }); testWidgets('Drag and drop - onLeave callback fires correctly', (WidgetTester tester) async { @@ -156,6 +162,83 @@ void main() { expect(leftBehind['Target 2'], equals(1)); }); + testWidgets('Drag and drop - onMove callback fires correctly', (WidgetTester tester) async { + final Map targetMoveCount = { + 'Target 1': 0, + 'Target 2': 0, + }; + + 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: const Text('Target 1')); + }, + onMove: (DragTargetDetails details) { + if (details.data is int) { + targetMoveCount['Target 1'] = + targetMoveCount['Target 1'] + (details.data as int); + } + }, + ), + DragTarget( + builder: (BuildContext context, List data, List rejects) { + return Container(height: 100.0, child: const Text('Target 2')); + }, + onMove: (DragTargetDetails details) { + if (details.data is int) { + targetMoveCount['Target 2'] = + targetMoveCount['Target 2'] + (details.data as int); + } + }, + ), + ], + ), + )); + + expect(targetMoveCount['Target 1'], equals(0)); + expect(targetMoveCount['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(targetMoveCount['Target 1'], equals(0)); + expect(targetMoveCount['Target 2'], equals(0)); + + final Offset secondLocation = tester.getCenter(find.text('Target 1')); + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(targetMoveCount['Target 1'], equals(1)); + expect(targetMoveCount['Target 2'], equals(0)); + + final Offset thirdLocation = tester.getCenter(find.text('Target 2')); + await gesture.moveTo(thirdLocation); + await tester.pump(); + + expect(targetMoveCount['Target 1'], equals(1)); + expect(targetMoveCount['Target 2'], equals(1)); + + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(targetMoveCount['Target 1'], equals(2)); + expect(targetMoveCount['Target 2'], equals(1)); + + await gesture.up(); + await tester.pump(); + + expect(targetMoveCount['Target 1'], equals(2)); + expect(targetMoveCount['Target 2'], equals(1)); + }); + testWidgets('Drag and drop - dragging over button', (WidgetTester tester) async { final List events = []; Offset firstLocation, secondLocation;