Exposed optional scrollController property in ReorderableListView (#49148)

diff --git a/packages/flutter/lib/src/material/reorderable_list.dart b/packages/flutter/lib/src/material/reorderable_list.dart
index 2944b1a..67023f4 100644
--- a/packages/flutter/lib/src/material/reorderable_list.dart
+++ b/packages/flutter/lib/src/material/reorderable_list.dart
@@ -62,6 +62,7 @@
     this.header,
     @required this.children,
     @required this.onReorder,
+    this.scrollController,
     this.scrollDirection = Axis.vertical,
     this.padding,
     this.reverse = false,
@@ -87,6 +88,15 @@
   /// 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 @@
         return _ReorderableListContent(
           header: widget.header,
           children: widget.children,
+          scrollController: widget.scrollController,
           scrollDirection: widget.scrollDirection,
           onReorder: widget.onReorder,
           padding: widget.padding,
@@ -165,6 +176,7 @@
   const _ReorderableListContent({
     @required this.header,
     @required this.children,
+    @required this.scrollController,
     @required this.scrollDirection,
     @required this.padding,
     @required this.onReorder,
@@ -173,6 +185,7 @@
 
   final Widget header;
   final List<Widget> children;
+  final ScrollController scrollController;
   final Axis scrollDirection;
   final EdgeInsets padding;
   final ReorderCallback onReorder;
@@ -262,7 +275,7 @@
 
   @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 94568e8..b9b2ec3 100644
--- a/packages/flutter/test/material/reorderable_list_test.dart
+++ b/packages/flutter/test/material/reorderable_list_test.dart
@@ -263,6 +263,65 @@
         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 <Widget>[
+                    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 <Widget>[