Флаттер отличается от вашего обычного приложения. Он использует принципы, схожие по реакции, которые сами по себе похожи на функциональное программирование
Это означает, что флаттеры имеют ряд ограничений, которые вынуждают вас по-разному проектировать ваше приложение:
- Виджеты неизменны. Вы не обновляете их. Вы воссоздаете их.
- Вы не можете получить доступ к состоянию ваших детей. Вы можете получить доступ к данным / состоянию только от своих родителей.
И в качестве награды вы можете быть почти уверены, что любое событие не имеет неявных неизвестных последствий для другого несвязанного класса.
Хорошо, но что это изменит в моем примере?
Это довольно просто: это означает, что невозможно , скажем, для Списка, получить доступ к информации из одного из элементов списка.
Вместо этого вы должны сделать следующее: хранить информацию о всех элементах списка внутри списка. И передай это каждому.
Итак, сначала нам нужно изменить ListItem
, чтобы он получал все от своего родителя. Мы также можем сделать его без сохранения состояния, потому что оно больше не хранит никакой информации.
С этими словами ListItem
внезапно становится довольно простым:
class ListItem extends StatelessWidget {
final String title;
final bool isSelected;
final ValueChanged<bool> onChange;
ListItem({ @required this.onChange, this.title = "", this.isSelected = false, });
@override
Widget build(BuildContext context) {
return new CheckboxListTile(
title: new Text(title),
value: isSelected,
onChanged: onChange
);
}
}
Обратите внимание, что все поля неизменны. И что событие onChange
, переданное CheckboxListTile
, также передается как параметр!
В этот момент вы можете подумать: «Но разве этот класс сейчас не имеет смысла»?
На самом деле, нет. В нашем случае макет очень прост. Но это хорошая практика для создания таких классов, так как это разбивает макет. Очистка кода родителя для логики макета.
В любом случае, давайте продолжим.
Теперь давайте изменим родительский объект, чтобы он содержал информацию, и передадим его:
class ItemsList extends StatefulWidget {
State<StatefulWidget> createState() => new ItemsListState();
}
class ItemsListState extends State<ItemsList> {
List<ListItem> items = new List<ListItem>();
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new ListView(
children: items,
),
floatingActionButton: new FloatingActionButton(
onPressed: addItem,
child: new Icon(Icons.add),
),
);
}
void addItem() {
final listItemIndex = items.length;
final newList = new List<ListItem>.from(items)
..add(
new ListItem(
onChange: (checked) => onListItemChange(listItemIndex, checked),
title: "Item n' $listItemIndex",
isSelected: false,
),
);
this.setState(() {
items = newList;
});
}
onListItemChange(int listItemIndex, bool checked) {
final newList = new List<ListItem>.from(items);
final currentItem = items[listItemIndex];
newList[listItemIndex] = new ListItem(
onChange: currentItem.onChange,
isSelected: checked,
title: currentItem.title,
);
this.setState(() {
items = newList;
});
}
}
Обратите внимание, что вместо того, чтобы поменять список items
, я каждый раз создаю новый.
Почему это? Это потому, что, как я объяснил ранее, поля должны быть неизменными. ItemsList
может быть с состоянием, но это не причина не следовать этому принципу. Поэтому вместо редактирования списка мы создаем новый на основе старого.
Факт в том, что без этого нового списка , ListView
подумал бы: «Эй, ты прислал мне тот же список, что и раньше. Так что мне не нужно обновлять права?».
Самое классное то, что по умолчанию ItemsList
содержит всю информацию о ListItem
. Так что getSelectedTitles
становится довольно легко сделать:
Iterable<String> getSelectedTitles() {
return items.where((item) => item.isSelected).map((item) => item.title);
}