1// Copyright 2018 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'package:flutter/foundation.dart'; 6import 'package:flutter/material.dart'; 7import 'package:flutter/rendering.dart'; 8 9import '../../gallery/demo.dart'; 10 11enum _ReorderableListType { 12 /// A list tile that contains a [CircleAvatar]. 13 horizontalAvatar, 14 15 /// A list tile that contains a [CircleAvatar]. 16 verticalAvatar, 17 18 /// A list tile that contains three lines of text and a checkbox. 19 threeLine, 20} 21 22class ReorderableListDemo extends StatefulWidget { 23 const ReorderableListDemo({ Key key }) : super(key: key); 24 25 static const String routeName = '/material/reorderable-list'; 26 27 @override 28 _ListDemoState createState() => _ListDemoState(); 29} 30 31class _ListItem { 32 _ListItem(this.value, this.checkState); 33 34 final String value; 35 36 bool checkState; 37} 38 39class _ListDemoState extends State<ReorderableListDemo> { 40 static final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); 41 42 PersistentBottomSheetController<void> _bottomSheet; 43 _ReorderableListType _itemType = _ReorderableListType.threeLine; 44 bool _reverse = false; 45 bool _reverseSort = false; 46 final List<_ListItem> _items = <String>[ 47 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 48 ].map<_ListItem>((String item) => _ListItem(item, false)).toList(); 49 50 void changeItemType(_ReorderableListType type) { 51 setState(() { 52 _itemType = type; 53 }); 54 // Rebuild the bottom sheet to reflect the selected list view. 55 _bottomSheet?.setState(() { 56 // Trigger a rebuild. 57 }); 58 // Close the bottom sheet to give the user a clear view of the list. 59 _bottomSheet?.close(); 60 } 61 62 void changeReverse(bool newValue) { 63 setState(() { 64 _reverse = newValue; 65 }); 66 // Rebuild the bottom sheet to reflect the selected list view. 67 _bottomSheet?.setState(() { 68 // Trigger a rebuild. 69 }); 70 // Close the bottom sheet to give the user a clear view of the list. 71 _bottomSheet?.close(); 72 } 73 74 void _showConfigurationSheet() { 75 setState(() { 76 _bottomSheet = scaffoldKey.currentState.showBottomSheet<void>((BuildContext bottomSheetContext) { 77 return DecoratedBox( 78 decoration: const BoxDecoration( 79 border: Border(top: BorderSide(color: Colors.black26)), 80 ), 81 child: ListView( 82 shrinkWrap: true, 83 primary: false, 84 children: <Widget>[ 85 CheckboxListTile( 86 dense: true, 87 title: const Text('Reverse'), 88 value: _reverse, 89 onChanged: changeReverse, 90 ), 91 RadioListTile<_ReorderableListType>( 92 dense: true, 93 title: const Text('Horizontal Avatars'), 94 value: _ReorderableListType.horizontalAvatar, 95 groupValue: _itemType, 96 onChanged: changeItemType, 97 ), 98 RadioListTile<_ReorderableListType>( 99 dense: true, 100 title: const Text('Vertical Avatars'), 101 value: _ReorderableListType.verticalAvatar, 102 groupValue: _itemType, 103 onChanged: changeItemType, 104 ), 105 RadioListTile<_ReorderableListType>( 106 dense: true, 107 title: const Text('Three-line'), 108 value: _ReorderableListType.threeLine, 109 groupValue: _itemType, 110 onChanged: changeItemType, 111 ), 112 ], 113 ), 114 ); 115 }); 116 117 // Garbage collect the bottom sheet when it closes. 118 _bottomSheet.closed.whenComplete(() { 119 if (mounted) { 120 setState(() { 121 _bottomSheet = null; 122 }); 123 } 124 }); 125 }); 126 } 127 128 Widget buildListTile(_ListItem item) { 129 const Widget secondary = Text( 130 'Even more additional list item information appears on line three.', 131 ); 132 Widget listTile; 133 switch (_itemType) { 134 case _ReorderableListType.threeLine: 135 listTile = CheckboxListTile( 136 key: Key(item.value), 137 isThreeLine: true, 138 value: item.checkState ?? false, 139 onChanged: (bool newValue) { 140 setState(() { 141 item.checkState = newValue; 142 }); 143 }, 144 title: Text('This item represents ${item.value}.'), 145 subtitle: secondary, 146 secondary: const Icon(Icons.drag_handle), 147 ); 148 break; 149 case _ReorderableListType.horizontalAvatar: 150 case _ReorderableListType.verticalAvatar: 151 listTile = Container( 152 key: Key(item.value), 153 height: 100.0, 154 width: 100.0, 155 child: CircleAvatar(child: Text(item.value), 156 backgroundColor: Colors.green, 157 ), 158 ); 159 break; 160 } 161 162 return listTile; 163 } 164 165 void _onReorder(int oldIndex, int newIndex) { 166 setState(() { 167 if (newIndex > oldIndex) { 168 newIndex -= 1; 169 } 170 final _ListItem item = _items.removeAt(oldIndex); 171 _items.insert(newIndex, item); 172 }); 173 } 174 175 176 @override 177 Widget build(BuildContext context) { 178 return Scaffold( 179 key: scaffoldKey, 180 appBar: AppBar( 181 title: const Text('Reorderable list'), 182 actions: <Widget>[ 183 MaterialDemoDocumentationButton(ReorderableListDemo.routeName), 184 IconButton( 185 icon: const Icon(Icons.sort_by_alpha), 186 tooltip: 'Sort', 187 onPressed: () { 188 setState(() { 189 _reverseSort = !_reverseSort; 190 _items.sort((_ListItem a, _ListItem b) => _reverseSort ? b.value.compareTo(a.value) : a.value.compareTo(b.value)); 191 }); 192 }, 193 ), 194 IconButton( 195 icon: Icon( 196 Theme.of(context).platform == TargetPlatform.iOS 197 ? Icons.more_horiz 198 : Icons.more_vert, 199 ), 200 tooltip: 'Show menu', 201 onPressed: _bottomSheet == null ? _showConfigurationSheet : null, 202 ), 203 ], 204 ), 205 body: Scrollbar( 206 child: ReorderableListView( 207 header: _itemType != _ReorderableListType.threeLine 208 ? Padding( 209 padding: const EdgeInsets.all(8.0), 210 child: Text('Header of the list', style: Theme.of(context).textTheme.headline)) 211 : null, 212 onReorder: _onReorder, 213 reverse: _reverse, 214 scrollDirection: _itemType == _ReorderableListType.horizontalAvatar ? Axis.horizontal : Axis.vertical, 215 padding: const EdgeInsets.symmetric(vertical: 8.0), 216 children: _items.map<Widget>(buildListTile).toList(), 217 ), 218 ), 219 ); 220 } 221} 222