diff --git a/packages/flutter/lib/src/widgets/reorderable_list.dart b/packages/flutter/lib/src/widgets/reorderable_list.dart index e5594a281b8..c838f137dc2 100644 --- a/packages/flutter/lib/src/widgets/reorderable_list.dart +++ b/packages/flutter/lib/src/widgets/reorderable_list.dart @@ -6,6 +6,7 @@ import 'dart:math'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; import 'basic.dart'; import 'debug.dart'; @@ -521,6 +522,15 @@ class SliverReorderableListState extends State with Ticke Offset? _finalDropPosition; MultiDragGestureRecognizer? _recognizer; bool _autoScrolling = false; + // To implement the gap for the dragged item, we replace the dragged item + // with a zero sized box, and then translate all of the later items down + // by the size of the dragged item. This allows us to keep the order of the + // list, while still being able to animate the gap between the items. However + // for the first frame of the drag, the item has not yet been replaced, so + // the calculation for the gap is off by the size of the gap. This flag is + // used to determine if the transition to the zero sized box has completed, + // so the gap calculation can compensate for it. + bool _dragStartTransitionComplete = false; late ScrollableState _scrollable; Axis get _scrollDirection => axisDirectionToAxis(_scrollable.axisDirection); @@ -615,6 +625,10 @@ class SliverReorderableListState extends State with Ticke final _ReorderableItemState item = _items[_dragIndex!]!; item.dragging = true; item.rebuild(); + _dragStartTransitionComplete = false; + SchedulerBinding.instance!.addPostFrameCallback((Duration duration) { + _dragStartTransitionComplete = true; + }); _insertIndex = item.index; _dragInfo = _DragInfo( @@ -722,7 +736,14 @@ class SliverReorderableListState extends State with Ticke if (item.index == _dragIndex! || !item.mounted) continue; - final Rect geometry = item.targetGeometry(); + Rect geometry = item.targetGeometry(); + if (!_dragStartTransitionComplete && _dragIndex! <= item.index) { + // Transition is not complete, so each item after the dragged item is still + // in its normal location and not moved up for the zero sized box that will + // replace the dragged item. + final Offset transitionOffset = _extentOffset(_reverse ? -gapExtent : gapExtent, _scrollDirection); + geometry = (geometry.topLeft - transitionOffset) & geometry.size; + } final double itemStart = _scrollDirection == Axis.vertical ? geometry.top : geometry.left; final double itemExtent = _scrollDirection == Axis.vertical ? geometry.height : geometry.width; final double itemEnd = itemStart + itemExtent; diff --git a/packages/flutter/test/widgets/reorderable_list_test.dart b/packages/flutter/test/widgets/reorderable_list_test.dart index 264bcc25bd7..f99edf024fd 100644 --- a/packages/flutter/test/widgets/reorderable_list_test.dart +++ b/packages/flutter/test/widgets/reorderable_list_test.dart @@ -122,7 +122,7 @@ void main() { expect(items, orderedEquals([0, 1, 2, 3, 4, 5, 6, 7])); // Drag item 0 downwards more than halfway to displace item 1. - await pressDragRelease(tester.getCenter(find.text('item 0')), const Offset(0, 151)); + await pressDragRelease(tester.getCenter(find.text('item 0')), const Offset(0, 51)); check(visible: [0, 1, 2, 3, 4, 5], hidden: [6, 7]); expect(tester.getTopLeft(find.text('item 1')), Offset.zero); expect(tester.getTopLeft(find.text('item 0')), const Offset(0, 100)); @@ -136,7 +136,7 @@ void main() { expect(items, orderedEquals([0, 1, 2, 3, 4, 5, 6, 7])); // Drag item 1 to item 3 - await pressDragRelease(tester.getCenter(find.text('item 1')), const Offset(0, 251)); + await pressDragRelease(tester.getCenter(find.text('item 1')), const Offset(0, 151)); check(visible: [0, 1, 2, 3, 4, 5], hidden: [6, 7]); expect(tester.getTopLeft(find.text('item 0')), Offset.zero); expect(tester.getTopLeft(find.text('item 1')), const Offset(0, 300)); @@ -433,7 +433,7 @@ void main() { await tester.pump(kPressTimeout); // Drag enough to move down the first item - await drag.moveBy(const Offset(0, 150)); + await drag.moveBy(const Offset(0, 50)); await tester.pump(); await drag.up(); await tester.pumpAndSettle();