Skip to content

Commit 5f9e069

Browse files
authored
Draggable feedback positioning (#145647)
Fixes a calculation in Draggable that was previously wrong when the target was transformed.
1 parent b974dcb commit 5f9e069

File tree

2 files changed

+134
-4
lines changed

2 files changed

+134
-4
lines changed

packages/flutter/lib/src/widgets/drag_target.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,7 @@ class _DragAvatar<T extends Object> extends Drag {
833833
final List<_DragTargetState<Object>> _enteredTargets = <_DragTargetState<Object>>[];
834834
Offset _position;
835835
Offset? _lastOffset;
836+
late Offset _overlayOffset;
836837
OverlayEntry? _entry;
837838

838839
@override
@@ -858,6 +859,10 @@ class _DragAvatar<T extends Object> extends Drag {
858859

859860
void updateDrag(Offset globalPosition) {
860861
_lastOffset = globalPosition - dragStartPoint;
862+
final RenderBox box = overlayState.context.findRenderObject()! as RenderBox;
863+
final Offset overlaySpaceOffset = box.globalToLocal(globalPosition);
864+
_overlayOffset = overlaySpaceOffset - dragStartPoint;
865+
861866
_entry!.markNeedsBuild();
862867
final HitTestResult result = HitTestResult();
863868
WidgetsBinding.instance.hitTestInView(result, globalPosition + feedbackOffset, viewId);
@@ -943,11 +948,9 @@ class _DragAvatar<T extends Object> extends Drag {
943948
}
944949

945950
Widget _build(BuildContext context) {
946-
final RenderBox box = overlayState.context.findRenderObject()! as RenderBox;
947-
final Offset overlayTopLeft = box.localToGlobal(Offset.zero);
948951
return Positioned(
949-
left: _lastOffset!.dx - overlayTopLeft.dx,
950-
top: _lastOffset!.dy - overlayTopLeft.dy,
952+
left: _overlayOffset.dx,
953+
top: _overlayOffset.dy,
951954
child: ExcludeSemantics(
952955
excluding: ignoringFeedbackSemantics,
953956
child: IgnorePointer(

packages/flutter/test/widgets/draggable_test.dart

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,10 @@ void main() {
967967
await gesture.moveTo(thirdLocation);
968968
await tester.pump();
969969
expect(tester.getTopLeft(find.text('N')), thirdLocation);
970+
971+
// Finish gesture to release resources.
972+
await gesture.up();
973+
await tester.pump();
970974
});
971975

972976
testWidgets('Horizontal axis draggable moves horizontally', (WidgetTester tester) async {
@@ -982,6 +986,10 @@ void main() {
982986
await gesture.moveTo(thirdLocation);
983987
await tester.pump();
984988
expect(tester.getTopLeft(find.text('H')), thirdLocation);
989+
990+
// Finish gesture to release resources.
991+
await gesture.up();
992+
await tester.pump();
985993
});
986994

987995
testWidgets('Horizontal axis draggable does not move vertically', (WidgetTester tester) async {
@@ -1000,6 +1008,10 @@ void main() {
10001008
await gesture.moveTo(thirdDragLocation);
10011009
await tester.pump();
10021010
expect(tester.getTopLeft(find.text('H')), thirdWidgetLocation);
1011+
1012+
// Finish gesture to release resources.
1013+
await gesture.up();
1014+
await tester.pump();
10031015
});
10041016

10051017
testWidgets('Vertical axis draggable moves vertically', (WidgetTester tester) async {
@@ -1015,6 +1027,10 @@ void main() {
10151027
await gesture.moveTo(thirdLocation);
10161028
await tester.pump();
10171029
expect(tester.getTopLeft(find.text('V')), thirdLocation);
1030+
1031+
// Finish gesture to release resources.
1032+
await gesture.up();
1033+
await tester.pump();
10181034
});
10191035

10201036
testWidgets('Vertical axis draggable does not move horizontally', (WidgetTester tester) async {
@@ -1033,6 +1049,10 @@ void main() {
10331049
await gesture.moveTo(thirdDragLocation);
10341050
await tester.pump();
10351051
expect(tester.getTopLeft(find.text('V')), thirdWidgetLocation);
1052+
1053+
// Finish gesture to release resources.
1054+
await gesture.up();
1055+
await tester.pump();
10361056
});
10371057
});
10381058

@@ -1666,6 +1686,10 @@ void main() {
16661686
expect(find.text('Dragging'), findsOneWidget);
16671687
expect(find.text('Target'), findsOneWidget);
16681688
expect(find.text('Rejected'), findsNothing);
1689+
1690+
// Finish gesture to release resources.
1691+
await gesture.up();
1692+
await tester.pump();
16691693
});
16701694

16711695

@@ -3099,6 +3123,10 @@ void main() {
30993123
),
31003124
findsNothing,
31013125
);
3126+
3127+
// Finish gesture to release resources.
3128+
await gesture.up();
3129+
await tester.pump();
31023130
});
31033131

31043132
testWidgets('Drag feedback is put on root overlay with [rootOverlay] flag', (WidgetTester tester) async {
@@ -3497,6 +3525,93 @@ void main() {
34973525
await tester.pumpAndSettle();
34983526
});
34993527

3528+
testWidgets('Drag and drop - feedback matches pointer in scaled MaterialApp', (WidgetTester tester) async {
3529+
await tester.pumpWidget(Transform.scale(
3530+
scale: 0.5,
3531+
child: const MaterialApp(
3532+
home: Scaffold(
3533+
body: Draggable<int>(
3534+
data: 42,
3535+
feedback: Text('Feedback'),
3536+
child: Text('Source'),
3537+
),
3538+
),
3539+
),
3540+
));
3541+
3542+
final Offset location = tester.getTopLeft(find.text('Source'));
3543+
final TestGesture gesture = await tester.startGesture(location);
3544+
final Offset secondLocation = location + const Offset(100, 100);
3545+
await gesture.moveTo(secondLocation);
3546+
await tester.pump();
3547+
final Offset appTopLeft = tester.getTopLeft(find.byType(MaterialApp));
3548+
expect(tester.getTopLeft(find.text('Source')), appTopLeft);
3549+
expect(tester.getTopLeft(find.text('Feedback')), secondLocation);
3550+
3551+
// Finish gesture to release resources.
3552+
await gesture.up();
3553+
await tester.pumpAndSettle();
3554+
});
3555+
3556+
testWidgets('Drag and drop - childDragAnchorStrategy works in scaled MaterialApp', (WidgetTester tester) async {
3557+
final Key sourceKey = UniqueKey();
3558+
final Key feedbackKey = UniqueKey();
3559+
await tester.pumpWidget(Transform.scale(
3560+
scale: 0.5,
3561+
child: MaterialApp(
3562+
home: Scaffold(
3563+
body: Draggable<int>(
3564+
data: 42,
3565+
feedback: Text('Text', key: feedbackKey),
3566+
child: Text('Text', key: sourceKey),
3567+
),
3568+
),
3569+
),
3570+
));
3571+
final Finder source = find.byKey(sourceKey);
3572+
final Finder feedback = find.byKey(feedbackKey);
3573+
3574+
final TestGesture gesture = await tester.startGesture(tester.getCenter(source));
3575+
await tester.pump();
3576+
expect(tester.getTopLeft(source), tester.getTopLeft(feedback));
3577+
3578+
// Finish gesture to release resources.
3579+
await gesture.up();
3580+
await tester.pumpAndSettle();
3581+
});
3582+
3583+
testWidgets('Drag and drop - feedback matches pointer in rotated MaterialApp', (WidgetTester tester) async {
3584+
await tester.pumpWidget(Transform.rotate(
3585+
angle: 1, // ~57 degrees
3586+
child: const MaterialApp(
3587+
home: Scaffold(
3588+
body: Draggable<int>(
3589+
data: 42,
3590+
feedback: Text('Feedback'),
3591+
child: Text('Source'),
3592+
),
3593+
),
3594+
),
3595+
));
3596+
3597+
final Offset location = tester.getTopLeft(find.text('Source'));
3598+
final TestGesture gesture = await tester.startGesture(location);
3599+
final Offset secondLocation = location + const Offset(100, 100);
3600+
await gesture.moveTo(secondLocation);
3601+
await tester.pump();
3602+
final Offset appTopLeft = tester.getTopLeft(find.byType(MaterialApp));
3603+
expect(tester.getTopLeft(find.text('Source')), appTopLeft);
3604+
final Offset feedbackTopLeft = tester.getTopLeft(find.text('Feedback'));
3605+
3606+
// Different rotations can incur rounding errors, this makes it more robust
3607+
expect(feedbackTopLeft.dx, moreOrLessEquals(secondLocation.dx));
3608+
expect(feedbackTopLeft.dy, moreOrLessEquals(secondLocation.dy));
3609+
3610+
// Finish gesture to release resources.
3611+
await gesture.up();
3612+
await tester.pumpAndSettle();
3613+
});
3614+
35003615
testWidgets('configurable Draggable hit test behavior', (WidgetTester tester) async {
35013616
const HitTestBehavior hitTestBehavior = HitTestBehavior.deferToChild;
35023617

@@ -3573,6 +3688,10 @@ void main() {
35733688

35743689
await tester.tap(find.text('Draggable'));
35753690
expect(onTap, true);
3691+
3692+
// Finish gesture to release resources.
3693+
await gesture.up();
3694+
await tester.pump();
35763695
});
35773696

35783697
testWidgets('configurable feedback ignore pointer behavior - LongPressDraggable', (WidgetTester tester) async {
@@ -3604,6 +3723,10 @@ void main() {
36043723

36053724
await tester.tap(find.text('Draggable'));
36063725
expect(onTap, true);
3726+
3727+
// Finish gesture to release resources.
3728+
await gesture.up();
3729+
await tester.pump();
36073730
});
36083731

36093732
testWidgets('configurable DragTarget hit test behavior', (WidgetTester tester) async {
@@ -3811,6 +3934,10 @@ Future<void> _testChildAnchorFeedbackPosition({ required WidgetTester tester, do
38113934
final Offset sourceTopLeft = tester.getTopLeft(find.text('Source'));
38123935
final Offset dragOffset = secondLocation - firstLocation;
38133936
expect(feedbackTopLeft, equals(sourceTopLeft + dragOffset));
3937+
3938+
// Finish gesture to release resources.
3939+
await gesture.up();
3940+
await tester.pump();
38143941
}
38153942

38163943
class DragTargetData { }

0 commit comments

Comments
 (0)