diff --git a/examples/flutter_gallery/lib/demo/material/reorderable_list_demo.dart b/examples/flutter_gallery/lib/demo/material/reorderable_list_demo.dart index f2961281d3..79b17be1d4 100644 --- a/examples/flutter_gallery/lib/demo/material/reorderable_list_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/reorderable_list_demo.dart @@ -41,6 +41,7 @@ class _ListDemoState extends State { PersistentBottomSheetController _bottomSheet; _ReorderableListType _itemType = _ReorderableListType.threeLine; + bool _reverse = false; bool _reverseSort = false; final List<_ListItem> _items = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', @@ -51,7 +52,21 @@ class _ListDemoState extends State { _itemType = type; }); // Rebuild the bottom sheet to reflect the selected list view. - _bottomSheet?.setState(() { }); + _bottomSheet?.setState(() { + // Trigger a rebuild. + }); + // Close the bottom sheet to give the user a clear view of the list. + _bottomSheet?.close(); + } + + void changeReverse(bool newValue) { + setState(() { + _reverse = newValue; + }); + // Rebuild the bottom sheet to reflect the selected list view. + _bottomSheet?.setState(() { + // Trigger a rebuild. + }); // Close the bottom sheet to give the user a clear view of the list. _bottomSheet?.close(); } @@ -67,6 +82,12 @@ class _ListDemoState extends State { shrinkWrap: true, primary: false, children: [ + CheckboxListTile( + dense: true, + title: const Text('Reverse'), + value: _reverse, + onChanged: changeReverse, + ), RadioListTile<_ReorderableListType>( dense: true, title: const Text('Horizontal Avatars'), @@ -189,6 +210,7 @@ class _ListDemoState extends State { child: Text('Header of the list', style: Theme.of(context).textTheme.headline)) : null, onReorder: _onReorder, + reverse: _reverse, scrollDirection: _itemType == _ReorderableListType.horizontalAvatar ? Axis.horizontal : Axis.vertical, padding: const EdgeInsets.symmetric(vertical: 8.0), children: _items.map(buildListTile).toList(), diff --git a/packages/flutter/lib/src/material/reorderable_list.dart b/packages/flutter/lib/src/material/reorderable_list.dart index e976720f20..17a38ac663 100644 --- a/packages/flutter/lib/src/material/reorderable_list.dart +++ b/packages/flutter/lib/src/material/reorderable_list.dart @@ -59,6 +59,7 @@ class ReorderableListView extends StatefulWidget { @required this.onReorder, this.scrollDirection = Axis.vertical, this.padding, + this.reverse = false, }): assert(scrollDirection != null), assert(onReorder != null), assert(children != null), @@ -83,6 +84,20 @@ class ReorderableListView extends StatefulWidget { /// The amount of space by which to inset the [children]. final EdgeInsets padding; + /// Whether the scroll view scrolls in the reading direction. + /// + /// For example, if the reading direction is left-to-right and + /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from + /// left to right when [reverse] is false and from right to left when + /// [reverse] is true. + /// + /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view + /// scrolls from top to bottom when [reverse] is false and from bottom to top + /// when [reverse] is true. + /// + /// Defaults to false. + final bool reverse; + /// Called when a list child is dropped into a new position to shuffle the /// underlying list. /// @@ -122,6 +137,7 @@ class _ReorderableListViewState extends State { scrollDirection: widget.scrollDirection, onReorder: widget.onReorder, padding: widget.padding, + reverse: widget.reverse, ); }, ); @@ -146,6 +162,7 @@ class _ReorderableListContent extends StatefulWidget { @required this.scrollDirection, @required this.padding, @required this.onReorder, + @required this.reverse, }); final Widget header; @@ -153,6 +170,7 @@ class _ReorderableListContent extends StatefulWidget { final Axis scrollDirection; final EdgeInsets padding; final ReorderCallback onReorder; + final bool reverse; @override _ReorderableListContentState createState() => _ReorderableListContentState(); @@ -544,16 +562,25 @@ class _ReorderableListContentState extends State<_ReorderableListContent> with T ); break; } - wrappedChildren.add(_wrap( - finalDropArea, - widget.children.length, - constraints), - ); + if (widget.reverse) { + wrappedChildren.insert(0, _wrap( + finalDropArea, + widget.children.length, + constraints), + ); + } else { + wrappedChildren.add(_wrap( + finalDropArea, + widget.children.length, + constraints), + ); + } return SingleChildScrollView( scrollDirection: widget.scrollDirection, child: _buildContainerForScrollDirection(children: wrappedChildren), padding: widget.padding, controller: _scrollController, + reverse: widget.reverse, ); }); } diff --git a/packages/flutter/test/material/reorderable_list_test.dart b/packages/flutter/test/material/reorderable_list_test.dart index 535c7b298b..1e76682ef0 100644 --- a/packages/flutter/test/material/reorderable_list_test.dart +++ b/packages/flutter/test/material/reorderable_list_test.dart @@ -900,6 +900,30 @@ void main() { }); + testWidgets('ReorderableListView can be reversed', (WidgetTester tester) async { + final Widget reorderableListView = ReorderableListView( + children: const [ + SizedBox( + key: Key('A'), + child: Text('A'), + ), + SizedBox( + key: Key('B'), + child: Text('B'), + ), + SizedBox( + key: Key('C'), + child: Text('C'), + ) + ], + reverse: true, + onReorder: (int oldIndex, int newIndex) {}, + ); + await tester.pumpWidget(MaterialApp( + home: reorderableListView, + )); + expect(tester.getCenter(find.text('A')), greaterThan(tester.getCenter(find.text('B')))); + }); // TODO(djshuckerow): figure out how to write a test for scrolling the list. }); }