diff --git a/packages/flutter/lib/src/material/reorderable_list.dart b/packages/flutter/lib/src/material/reorderable_list.dart index 2944b1adc03..67023f4b0da 100644 --- a/packages/flutter/lib/src/material/reorderable_list.dart +++ b/packages/flutter/lib/src/material/reorderable_list.dart @@ -62,6 +62,7 @@ class ReorderableListView extends StatefulWidget { this.header, @required this.children, @required this.onReorder, + this.scrollController, this.scrollDirection = Axis.vertical, this.padding, this.reverse = false, @@ -87,6 +88,15 @@ class ReorderableListView extends StatefulWidget { /// List [children] can only drag along this [Axis]. final Axis scrollDirection; + /// Creates a [ScrollPosition] to manage and determine which portion + /// of the content is visible in the scroll view. + /// + /// This can be used in many ways, such as setting an initial scroll offset, + /// (via [ScrollController.initialScrollOffset]), reading the current scroll position + /// (via [ScrollController.offset]), or changing it (via [ScrollController.jumpTo] or + /// [ScrollController.animateTo]). + final ScrollController scrollController; + /// The amount of space by which to inset the [children]. final EdgeInsets padding; @@ -140,6 +150,7 @@ class _ReorderableListViewState extends State { return _ReorderableListContent( header: widget.header, children: widget.children, + scrollController: widget.scrollController, scrollDirection: widget.scrollDirection, onReorder: widget.onReorder, padding: widget.padding, @@ -165,6 +176,7 @@ class _ReorderableListContent extends StatefulWidget { const _ReorderableListContent({ @required this.header, @required this.children, + @required this.scrollController, @required this.scrollDirection, @required this.padding, @required this.onReorder, @@ -173,6 +185,7 @@ class _ReorderableListContent extends StatefulWidget { final Widget header; final List children; + final ScrollController scrollController; final Axis scrollDirection; final EdgeInsets padding; final ReorderCallback onReorder; @@ -262,7 +275,7 @@ class _ReorderableListContentState extends State<_ReorderableListContent> with T @override void didChangeDependencies() { - _scrollController = PrimaryScrollController.of(context) ?? ScrollController(); + _scrollController = widget.scrollController ?? PrimaryScrollController.of(context) ?? ScrollController(); super.didChangeDependencies(); } diff --git a/packages/flutter/test/material/reorderable_list_test.dart b/packages/flutter/test/material/reorderable_list_test.dart index 94568e84a96..b9b2ec33b51 100644 --- a/packages/flutter/test/material/reorderable_list_test.dart +++ b/packages/flutter/test/material/reorderable_list_test.dart @@ -263,6 +263,65 @@ void main() { expect(scrollView.controller, primary2); }); + testWidgets('Test custom ScrollController behavior when set', (WidgetTester tester) async { + const Key firstBox = Key('C'); + const Key secondBox = Key('B'); + const Key thirdBox = Key('A'); + final ScrollController customController = ScrollController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox( + height: 200, + child: ReorderableListView( + scrollController: customController, + onReorder: (int oldIndex, int newIndex) { }, + children: const [ + SizedBox(width: 100.0, height: 100.0, child: Text('C'), key: firstBox), + SizedBox(width: 100.0, height: 100.0, child: Text('B'), key: secondBox), + SizedBox(width: 100.0, height: 100.0, child: Text('A'), key: thirdBox), + ], + ), + ), + ), + ), + ); + + // Check initial scroll offset of first list item relative to + // the offset of the list view. + customController.animateTo( + 40.0, + duration: const Duration(milliseconds: 200), + curve: Curves.linear + ); + await tester.pumpAndSettle(); + Offset listViewTopLeft = tester.getTopLeft( + find.byType(ReorderableListView), + ); + Offset firstBoxTopLeft = tester.getTopLeft( + find.byKey(firstBox) + ); + expect(firstBoxTopLeft.dy, listViewTopLeft.dy - 40.0); + + // Drag the UI to see if the scroll controller updates accordingly + await tester.drag( + find.text('B'), + const Offset(0.0, -100.0), + ); + listViewTopLeft = tester.getTopLeft( + find.byType(ReorderableListView), + ); + firstBoxTopLeft = tester.getTopLeft( + find.byKey(firstBox), + ); + // Initial scroll controller offset: 40.0 + // Drag UI by 100.0 upwards vertically + // First 20.0 px always ignored, so scroll offset is only + // shifted by 80.0. + // Final offset: 40.0 + 80.0 = 120.0 + expect(customController.offset, 120.0); + }); + testWidgets('Still builds when no PrimaryScrollController is available', (WidgetTester tester) async { final Widget reorderableList = ReorderableListView( children: const [