diff --git a/packages/flutter/lib/src/widgets/reorderable_list.dart b/packages/flutter/lib/src/widgets/reorderable_list.dart index fb7a77fcda1..3e7ed2796e9 100644 --- a/packages/flutter/lib/src/widgets/reorderable_list.dart +++ b/packages/flutter/lib/src/widgets/reorderable_list.dart @@ -522,6 +522,7 @@ class SliverReorderableListState extends State with Ticke int? _insertIndex; Offset? _finalDropPosition; MultiDragGestureRecognizer? _recognizer; + int? _recognizerPointer; 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 @@ -579,12 +580,18 @@ class SliverReorderableListState extends State with Ticke setState(() { if (_dragInfo != null) { cancelReorder(); + } else if (_recognizer != null && _recognizerPointer != event.pointer) { + _recognizer!.dispose(); + _recognizer = null; + _recognizerPointer = null; } + if (_items.containsKey(index)) { _dragIndex = index; _recognizer = recognizer ..onStart = _dragStart ..addPointer(event); + _recognizerPointer = event.pointer; } else { // TODO(darrenaustin): Can we handle this better, maybe scroll to the item? throw Exception('Attempting to start a drag on a non-visible item'); diff --git a/packages/flutter/test/widgets/reorderable_list_test.dart b/packages/flutter/test/widgets/reorderable_list_test.dart index 31b189282f5..e052213ff20 100644 --- a/packages/flutter/test/widgets/reorderable_list_test.dart +++ b/packages/flutter/test/widgets/reorderable_list_test.dart @@ -7,6 +7,53 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { + // Regression test for https://github.com/flutter/flutter/issues/88191 + testWidgets('Do not crash when dragging with two fingers simultaneously', (WidgetTester tester) async { + final List items = List.generate(3, (int index) => index); + void handleReorder(int fromIndex, int toIndex) { + if (toIndex > fromIndex) { + toIndex -= 1; + } + items.insert(toIndex, items.removeAt(fromIndex)); + } + + await tester.pumpWidget(MaterialApp( + home: ReorderableList( + itemBuilder: (BuildContext context, int index) { + return ReorderableDragStartListener( + index: index, + key: ValueKey(items[index]), + child: SizedBox( + height: 100, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('item ${items[index]}'), + ], + ), + ), + ); + }, + itemCount: items.length, + onReorder: handleReorder, + ), + )); + + final TestGesture drag1 = await tester.startGesture(tester.getCenter(find.text('item 0'))); + final TestGesture drag2 = await tester.startGesture(tester.getCenter(find.text('item 0'))); + await tester.pump(kLongPressTimeout); + + await drag1.moveBy(const Offset(0, 100)); + await drag2.moveBy(const Offset(0, 100)); + await tester.pumpAndSettle(); + + await drag1.up(); + await drag2.up(); + await tester.pumpAndSettle(); + + expect(tester.takeException(), isNull); + }); + testWidgets('negative itemCount should assert', (WidgetTester tester) async { final List items = [1, 2, 3]; await tester.pumpWidget(MaterialApp(