mirror of
https://github.com/flutter/flutter
synced 2024-08-24 18:36:03 +00:00
Fixed the issue of incorrect item position when prototypeItem is set in SliverReorderableList. (#142880)
Fixes #142708
This commit is contained in:
parent
73c26a1c0b
commit
bce848ebf9
|
@ -620,15 +620,6 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
|||
Offset? _finalDropPosition;
|
||||
MultiDragGestureRecognizer? _recognizer;
|
||||
int? _recognizerPointer;
|
||||
// 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;
|
||||
|
||||
EdgeDraggingAutoScroller? _autoScroller;
|
||||
|
||||
|
@ -733,7 +724,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
|||
|
||||
void _registerItem(_ReorderableItemState item) {
|
||||
if (_dragInfo != null && _items[item.index] != item) {
|
||||
item.updateForGap(_dragInfo!.index, _dragInfo!.itemExtent, false, _reverse);
|
||||
item.updateForGap(_dragInfo!.index, _dragInfo!.index, _dragInfo!.itemExtent, false, _reverse);
|
||||
}
|
||||
_items[item.index] = item;
|
||||
if (item.index == _dragInfo?.index) {
|
||||
|
@ -755,10 +746,6 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
|||
item.dragging = true;
|
||||
widget.onReorderStart?.call(_dragIndex!);
|
||||
item.rebuild();
|
||||
_dragStartTransitionComplete = false;
|
||||
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
|
||||
_dragStartTransitionComplete = true;
|
||||
}, debugLabel: 'SliverReorderableList.completeDragStartTransition');
|
||||
|
||||
_insertIndex = item.index;
|
||||
_dragInfo = _DragInfo(
|
||||
|
@ -783,7 +770,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
|||
if (childItem == item || !childItem.mounted) {
|
||||
continue;
|
||||
}
|
||||
childItem.updateForGap(_insertIndex!, _dragInfo!.itemExtent, false, _reverse);
|
||||
childItem.updateForGap(_insertIndex!, _insertIndex!, _dragInfo!.itemExtent, false, _reverse);
|
||||
}
|
||||
return _dragInfo;
|
||||
}
|
||||
|
@ -805,9 +792,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
|||
void _dragEnd(_DragInfo item) {
|
||||
setState(() {
|
||||
if (_insertIndex == item.index) {
|
||||
// Although it's at its original position, the original position has been replaced by a zero-size box
|
||||
// So when reversed, it should offset its own extent
|
||||
_finalDropPosition = _reverse ? _itemOffsetAt(_insertIndex!) - _extentOffset(item.itemExtent, _scrollDirection) : _itemOffsetAt(_insertIndex!);
|
||||
_finalDropPosition = _itemOffsetAt(_insertIndex!);
|
||||
} else if (_reverse) {
|
||||
if (_insertIndex! >= _items.length) {
|
||||
// Drop at the starting position of the last element and offset its own extent
|
||||
|
@ -890,14 +875,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
|||
continue;
|
||||
}
|
||||
|
||||
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 Rect geometry = item.targetGeometry();
|
||||
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;
|
||||
|
@ -952,7 +930,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
|||
if (item.index == _dragIndex! || !item.mounted) {
|
||||
continue;
|
||||
}
|
||||
item.updateForGap(newIndex, gapExtent, true, _reverse);
|
||||
item.updateForGap(_dragIndex!, newIndex, gapExtent, true, _reverse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1050,10 +1028,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
|||
assert(debugCheckHasOverlay(context));
|
||||
final SliverChildBuilderDelegate childrenDelegate = SliverChildBuilderDelegate(
|
||||
_itemBuilder,
|
||||
// When dragging, the dragged item is still in the list but has been replaced
|
||||
// by a zero height SizedBox, so that the gap can move around. To make the
|
||||
// list extent stable we add a dummy entry to the end.
|
||||
childCount: widget.itemCount + (_dragInfo != null ? 1 : 0),
|
||||
childCount: widget.itemCount,
|
||||
findChildIndexCallback: widget.findChildIndexCallback,
|
||||
);
|
||||
if (widget.itemExtent != null) {
|
||||
|
@ -1138,7 +1113,8 @@ class _ReorderableItemState extends State<_ReorderableItem> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_dragging) {
|
||||
return const SizedBox();
|
||||
final Size size = _extentSize(_listState._dragInfo!.itemExtent, _listState._scrollDirection);
|
||||
return SizedBox.fromSize(size: size);
|
||||
}
|
||||
_listState._registerItem(this);
|
||||
return Transform(
|
||||
|
@ -1161,10 +1137,18 @@ class _ReorderableItemState extends State<_ReorderableItem> {
|
|||
return _targetOffset;
|
||||
}
|
||||
|
||||
void updateForGap(int gapIndex, double gapExtent, bool animate, bool reverse) {
|
||||
final Offset newTargetOffset = (gapIndex <= index)
|
||||
? _extentOffset(reverse ? -gapExtent : gapExtent, _listState._scrollDirection)
|
||||
: Offset.zero;
|
||||
void updateForGap(int dragIndex, int gapIndex, double gapExtent, bool animate, bool reverse) {
|
||||
// An offset needs to be added to create a gap when we are between the
|
||||
// moving element (dragIndex) and the current gap position (gapIndex).
|
||||
// For how to update the gap position, refer to [_dragUpdateItems].
|
||||
final Offset newTargetOffset;
|
||||
if (gapIndex < dragIndex && index < dragIndex && index >= gapIndex) {
|
||||
newTargetOffset = _extentOffset(reverse ? -gapExtent : gapExtent, _listState._scrollDirection);
|
||||
} else if (gapIndex > dragIndex && index > dragIndex && index < gapIndex) {
|
||||
newTargetOffset = _extentOffset(reverse ? gapExtent : -gapExtent, _listState._scrollDirection);
|
||||
} else {
|
||||
newTargetOffset = Offset.zero;
|
||||
}
|
||||
if (newTargetOffset != _targetOffset) {
|
||||
_targetOffset = newTargetOffset;
|
||||
if (animate) {
|
||||
|
@ -1504,6 +1488,15 @@ double _sizeExtent(Size size, Axis scrollDirection) {
|
|||
};
|
||||
}
|
||||
|
||||
Size _extentSize(double extent, Axis scrollDirection) {
|
||||
switch (scrollDirection) {
|
||||
case Axis.horizontal:
|
||||
return Size(extent, 0);
|
||||
case Axis.vertical:
|
||||
return Size(0, extent);
|
||||
}
|
||||
}
|
||||
|
||||
double _offsetExtent(Offset offset, Axis scrollDirection) {
|
||||
return switch (scrollDirection) {
|
||||
Axis.horizontal => offset.dx,
|
||||
|
|
|
@ -1483,6 +1483,51 @@ void main() {
|
|||
await testMove(element.$1, element.$2, reverse: true, scrollDirection: Axis.horizontal);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('Tests that the item position is correct when prototypeItem or itemExtent are set', (WidgetTester tester) async {
|
||||
Future<void> pumpFor({Widget? prototypeItem, double? itemExtent}) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverReorderableList(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ReorderableDragStartListener(
|
||||
key: ValueKey<int>(index),
|
||||
index: index,
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
child: Text('$index'),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: 5,
|
||||
itemExtent: itemExtent,
|
||||
prototypeItem: prototypeItem,
|
||||
onReorder: (int fromIndex, int toIndex) {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> testFor({Widget? prototypeItem, double? itemExtent}) async {
|
||||
await pumpFor(prototypeItem: prototypeItem, itemExtent: itemExtent);
|
||||
final TestGesture drag = await tester.startGesture(tester.getCenter(find.text('0')));
|
||||
await tester.pump(kLongPressTimeout);
|
||||
await drag.moveBy(const Offset(0, 20));
|
||||
await tester.pump();
|
||||
expect(tester.getTopLeft(find.text('1')), const Offset(0, 100));
|
||||
await drag.up();
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
await testFor();
|
||||
await testFor(prototypeItem: const SizedBox(height: 100, width: 100, child: Text('prototype')));
|
||||
await testFor(itemExtent: 100);
|
||||
});
|
||||
}
|
||||
|
||||
class TestList extends StatelessWidget {
|
||||
|
|
Loading…
Reference in a new issue