Add optional DragAnchorStrategy to Draggable (#73143)

This commit is contained in:
Johannes von Bargen 2021-03-17 00:48:03 +01:00 committed by GitHub
parent 4807f806e2
commit 4b150b135f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 11 deletions

View file

@ -116,6 +116,7 @@ class ExampleDragSource extends StatelessWidget {
Offset feedbackOffset;
DragAnchor anchor;
DragAnchorStrategy dragAnchorStrategy;
if (!under) {
feedback = Transform(
transform: Matrix4.identity()
@ -124,9 +125,11 @@ class ExampleDragSource extends StatelessWidget {
);
feedbackOffset = const Offset(0.0, -kFingerSize);
anchor = DragAnchor.pointer;
dragAnchorStrategy = pointerDragAnchorStrategy;
} else {
feedbackOffset = Offset.zero;
anchor = DragAnchor.child;
dragAnchorStrategy = childDragAnchorStrategy;
}
if (heavy) {
@ -143,7 +146,7 @@ class ExampleDragSource extends StatelessWidget {
child: contents,
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchor: anchor,
dragAnchorStrategy: dragAnchorStrategy,
);
}
}

View file

@ -66,6 +66,24 @@ typedef DragTargetLeave<T> = void Function(T? data);
/// Used by [DragTarget.onMove].
typedef DragTargetMove<T> = void Function(DragTargetDetails<T> details);
/// Signature for the strategy that determines the drag start point.
///
/// Used for the built-in strategies switched via [DragAnchor] and the optinally
/// injectable [Draggable.dragAnchorStrategy]
typedef DragAnchorStrategy = Offset Function(Draggable<Object> draggable, BuildContext context, Offset position);
/// The default [DragAnchorStrategy] used when [Draggable.dragAnchor] is not set
/// or set to [DragAnchor.child]
Offset childDragAnchorStrategy(Draggable<Object> draggable, BuildContext context, Offset position) {
final RenderBox renderObject = context.findRenderObject()! as RenderBox;
return renderObject.globalToLocal(position);
}
/// The [DragAnchorStrategy] used when [Draggable.dragAnchor] set to
/// [DragAnchor.pointer]
Offset pointerDragAnchorStrategy(Draggable<Object> draggable, BuildContext context, Offset position) {
return Offset.zero;
}
/// Where the [Draggable] should be anchored during a drag.
enum DragAnchor {
/// Display the feedback anchored at the position of the original child. If
@ -191,7 +209,12 @@ class Draggable<T extends Object> extends StatefulWidget {
this.axis,
this.childWhenDragging,
this.feedbackOffset = Offset.zero,
@Deprecated(
'Use dragAnchorStrategy instead. '
'This feature was deprecated after v2.1.0-10.0.pre.'
)
this.dragAnchor = DragAnchor.child,
this.dragAnchorStrategy,
this.affinity,
this.maxSimultaneousDrags,
this.onDragStarted,
@ -263,8 +286,23 @@ class Draggable<T extends Object> extends StatefulWidget {
final Offset feedbackOffset;
/// Where this widget should be anchored during a drag.
///
/// This property is overridden by the [dragAnchorStrategy] if the latter is provided.
///
/// Defaults to [DragAnchor.child].
@Deprecated(
'Use dragAnchorStrategy instead. '
'This feature was deprecated after v2.1.0-10.0.pre.'
)
final DragAnchor dragAnchor;
/// A strategy that is used by this draggable to get the the anchor offset when it is dragged.
///
/// The anchor offset refers to the distance between the users' fingers and the [feedback] widget when this draggable is dragged.
///
/// Defaults to [childDragAnchorStrategy] if the [dragAnchor] is set to [DragAnchor.child] or [pointerDragAnchorStrategy] if the [dragAnchor] is set to [DragAnchor.pointer].
final DragAnchorStrategy? dragAnchorStrategy;
/// Whether the semantics of the [feedback] widget is ignored when building
/// the semantics tree.
///
@ -308,7 +346,7 @@ class Draggable<T extends Object> extends StatefulWidget {
/// Called when the draggable starts being dragged.
final VoidCallback? onDragStarted;
/// Called when the draggable is being dragged.
/// Called when the draggable is dragged.
///
/// This function will only be called while this widget is still mounted to
/// the tree (i.e. [State.mounted] is true), and if this widget has actually moved.
@ -487,14 +525,17 @@ class _DraggableState<T extends Object> extends State<Draggable<T>> {
if (widget.maxSimultaneousDrags != null && _activeCount >= widget.maxSimultaneousDrags!)
return null;
final Offset dragStartPoint;
switch (widget.dragAnchor) {
case DragAnchor.child:
final RenderBox renderObject = context.findRenderObject()! as RenderBox;
dragStartPoint = renderObject.globalToLocal(position);
break;
case DragAnchor.pointer:
dragStartPoint = Offset.zero;
break;
if (widget.dragAnchorStrategy == null) {
switch (widget.dragAnchor) {
case DragAnchor.child:
dragStartPoint = childDragAnchorStrategy(widget, context, position);
break;
case DragAnchor.pointer:
dragStartPoint = pointerDragAnchorStrategy(widget, context, position);
break;
}
} else {
dragStartPoint = widget.dragAnchorStrategy!(widget, context, position);
}
setState(() {
_activeCount += 1;

View file

@ -2540,7 +2540,6 @@ void main() {
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
@ -3019,6 +3018,30 @@ void main() {
semantics.dispose();
});
testWidgets('Drag and drop - when a dragAnchorStrategy is provided it gets called', (WidgetTester tester) async {
bool dragAnchorStrategyCalled = false;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
child: const Text('Source'),
feedback: const Text('Feedback'),
dragAnchorStrategy: (Draggable<Object> widget, BuildContext context, Offset position) {
dragAnchorStrategyCalled = true;
return const Offset(0, 0);
}
)
],
),
));
final Offset location = tester.getCenter(find.text('Source'));
await tester.startGesture(location, pointer: 7);
expect(dragAnchorStrategyCalled, true);
});
testWidgets('configurable Draggable hit test behavior', (WidgetTester tester) async {
const HitTestBehavior hitTestBehavior = HitTestBehavior.deferToChild;