Этого можно добиться, установив атрибут children
виджета ExpansionTile
.
Подход заключается в следующем.
- Вам необходим общий обработчик данных или какое-либо управление состоянием, чтобы сохранить состояние родителя и потомков за пределами виджета, чтобы избежать перестроений при изменении дочернего списка. Для краткости я использую синглтон для хранения общих данных. В реальном случае это должен быть подход, основанный на
ChangeNotifier
или BLoc
. Однако, если вы изменяете родительский или дочерний список, вам нужна полная перестройка, потому что виджеты Flutter неизменны.
/// Holding the common data as a singleton to avoid excessive rebuilds.
/// Usually this should be replaced with a manager or bloc or changenotifier class
class DataHolder {
List<String> parentKeys;
Map<String, List<String>> childMap;
DataHolder._privateConstructor();
static final DataHolder _dataHolder = DataHolder._privateConstructor();
static DataHolder get instance => _dataHolder;
factory DataHolder.initialize({@required parentKeys}) {
_dataHolder.parentKeys = parentKeys;
_dataHolder.childMap = {};
for (String key in parentKeys) {
_dataHolder.childMap.putIfAbsent(
key, () => ['${key}child_1', '${key}child_2', '${key}child_3']);
}
return _dataHolder;
}
}
Создайте виджет, который возвращает дочерний элемент
ReorderableListView
с уникальным
ScrollController
для каждого из этих виджетов. Например,
ReorderList
виджет. Это почти идентично тому, что вы написали, за исключением того, что я возвращаю
ListTile
вместо
ExpansionTile
и устанавливаю атрибут
scrollController
. Текущая стабильная версия не имеет этого атрибута. Таким образом, в этом решении он обернут в
PrimaryScrollController
виджет, чтобы избежать двойного использования scrollController.
class ReorderList extends StatefulWidget {
final String parentMapKey;
ReorderList({this.parentMapKey});
@override
_ReorderListState createState() => _ReorderListState();
}
class _ReorderListState extends State<ReorderList> {
@override
Widget build(BuildContext context) {
return PrimaryScrollController(
controller: ScrollController(),
child: ReorderableListView(
// scrollController: ScrollController(),
onReorder: onReorder,
children: DataHolder.instance.childMap[widget.parentMapKey]
.map(
(String child) => ListTile(
key: ValueKey(child),
leading: Icon(Icons.done_all),
title: Text(child),
),
)
.toList(),
),
);
}
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
List<String> children = DataHolder.instance.childMap[widget.parentMapKey];
String game = children[oldIndex];
children.removeAt(oldIndex);
children.insert(newIndex, game);
DataHolder.instance.childMap[widget.parentMapKey] = children;
// Need to set state to rebuild the children.
setState(() {});
}
}
В родительском виджете
ExpansionTile
установите для этого нового виджета один из
children
. Этот дочерний элемент и родительский элемент созданы из значения одноэлементного класса
DataHolder
.
Примечание. Я устанавливаю постоянную высоту, чтобы избежать конфликтов макета. Вы должны поиграть с этим для динамических c размеров.
class ReorderItems extends StatefulWidget {
final List<String> topTen;
ReorderItems({this.topTen});
@override
_ReorderItemsState createState() => _ReorderItemsState();
}
class _ReorderItemsState extends State<ReorderItems> {
@override
void initState() {
super.initState();
// initialize the children for the Expansion tile
// This initialization can be replaced with any logic like network fetch or something else.
DataHolder.initialize(parentKeys: widget.topTen);
}
@override
Widget build(BuildContext context) {
return PrimaryScrollController(
key: ValueKey(widget.topTen.toString()),
controller: ScrollController(),
child: ReorderableListView(
onReorder: onReorder,
children: getListItem(),
),
);
}
List<ExpansionTile> getListItem() => DataHolder.instance.parentKeys
.asMap()
.map((index, item) => MapEntry(index, buildTenableListTile(item, index)))
.values
.toList();
ExpansionTile buildTenableListTile(String mapKey, int index) => ExpansionTile(
key: ValueKey(mapKey),
title: Text(mapKey),
leading: Icon(Icons.list),
backgroundColor: Colors.blue,
children: [
Container(
key: ValueKey('$mapKey$index'),
height: 400,
child: Container(
padding: EdgeInsets.only(left: 20.0),
child: ReorderList(
parentMapKey: mapKey,
),
),
),
],
);
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
setState(() {
String game = DataHolder.instance.parentKeys[oldIndex];
DataHolder.instance.parentKeys.removeAt(oldIndex);
DataHolder.instance.parentKeys.insert(newIndex, game);
});
}
}
В этом codepen доступно полностью рабочее решение. Я изменил код, чтобы динамически принимать список элементов из родительского виджета. Вам придется поиграть с тем, как сохранить данные и сократить перестроения. Но в целом ребенок ReorderableListView
работает, пока поддерживается список.
Надеюсь, это поможет.