Commit b7f6be6f authored by Tom Larsen's avatar Tom Larsen Committed by Ian Hickson

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.
parent 47e52b82
...@@ -37,6 +37,11 @@ typedef Widget DragTargetBuilder<T>(BuildContext context, List<T> candidateData, ...@@ -37,6 +37,11 @@ typedef Widget DragTargetBuilder<T>(BuildContext context, List<T> candidateData,
/// Used by [Draggable.onDraggableCanceled]. /// Used by [Draggable.onDraggableCanceled].
typedef void DraggableCanceledCallback(Velocity velocity, Offset offset); typedef void DraggableCanceledCallback(Velocity velocity, Offset offset);
/// Signature for when a [Draggable] leaves a [DragTarget].
///
/// Used by [DragTarget.onLeave].
typedef void DragTargetLeave<T>(T data);
/// Where the [Draggable] should be anchored during a drag. /// Where the [Draggable] should be anchored during a drag.
enum DragAnchor { enum DragAnchor {
/// Display the feedback anchored at the position of the original child. If /// Display the feedback anchored at the position of the original child. If
...@@ -372,7 +377,8 @@ class DragTarget<T> extends StatefulWidget { ...@@ -372,7 +377,8 @@ class DragTarget<T> extends StatefulWidget {
Key key, Key key,
@required this.builder, @required this.builder,
this.onWillAccept, this.onWillAccept,
this.onAccept this.onAccept,
this.onLeave,
}) : super(key: key); }) : super(key: key);
/// Called to build the contents of this widget. /// Called to build the contents of this widget.
...@@ -383,11 +389,19 @@ class DragTarget<T> extends StatefulWidget { ...@@ -383,11 +389,19 @@ class DragTarget<T> extends StatefulWidget {
/// Called to determine whether this widget is interested in receiving a given /// Called to determine whether this widget is interested in receiving a given
/// piece of data being dragged over this drag target. /// 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<T> onWillAccept; final DragTargetWillAccept<T> onWillAccept;
/// Called when an acceptable piece of data was dropped over this drag target. /// Called when an acceptable piece of data was dropped over this drag target.
final DragTargetAccept<T> onAccept; final DragTargetAccept<T> onAccept;
/// Called when a given piece of data being dragged over this target leaves
/// the target.
final DragTargetLeave<T> onLeave;
@override @override
_DragTargetState<T> createState() => new _DragTargetState<T>(); _DragTargetState<T> createState() => new _DragTargetState<T>();
} }
...@@ -421,6 +435,8 @@ class _DragTargetState<T> extends State<DragTarget<T>> { ...@@ -421,6 +435,8 @@ class _DragTargetState<T> extends State<DragTarget<T>> {
_candidateAvatars.remove(avatar); _candidateAvatars.remove(avatar);
_rejectedAvatars.remove(avatar); _rejectedAvatars.remove(avatar);
}); });
if (widget.onLeave != null)
widget.onLeave(avatar.data);
} }
void didDrop(_DragAvatar<dynamic> avatar) { void didDrop(_DragAvatar<dynamic> avatar) {
......
...@@ -68,6 +68,73 @@ void main() { ...@@ -68,6 +68,73 @@ void main() {
expect(dragStartedCount, 1); expect(dragStartedCount, 1);
}); });
testWidgets('Drag and drop - onLeave callback fires correctly', (WidgetTester tester) async {
final Map<String,int> leftBehind = <String,int>{
'Target 1': 0,
'Target 2': 0,
};
await tester.pumpWidget(new MaterialApp(
home: new Column(
children: <Widget>[
const Draggable<int>(
data: 1,
child: const Text('Source'),
feedback: const Text('Dragging'),
),
new DragTarget<int>(
builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
return new Container(height: 100.0, child: const Text('Target 1'));
},
onLeave: (int data) => leftBehind['Target 1'] = leftBehind['Target 1'] + data,
),
new DragTarget<int>(
builder: (BuildContext context, List<int> data, List<dynamic> 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 { testWidgets('Drag and drop - dragging over button', (WidgetTester tester) async {
final List<String> events = <String>[]; final List<String> events = <String>[];
Offset firstLocation, secondLocation; Offset firstLocation, secondLocation;
......
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