Списки и сетки
ListView и GridView выводят наборы элементов — списком и плиткой. Оба рассчитаны на данные, которых заранее неизвестно сколько: картинки, тексты, карточки товаров.
ListView
ListView — прокручиваемый список виджетов, выстроенных в линию: по вертикали или по горизонтали.
Главное его свойство — отрисовываются только видимые элементы. Список на тысячу позиций стоит примерно столько же, сколько список на десять.
Настройки такие:
Направление
Axis задаёт ориентацию списка: Vertical или Horizontal.

Расстояния
- Items Spacing — промежуток между соседними элементами в пикселях.
В списках, собранных вручную, задавайте Items Spacing на родителе, а не отступы у каждого элемента: так промежутки гарантированно одинаковые.
-
Apply to Start & End — добавить такой же отступ в начале и в конце списка.
-
Start Spacing и End Spacing — отдельные значения для начала и конца, независимо от промежутков между элементами.
Дополнительные свойства
-
Shrink Wrap — список занимает ровно столько места, сколько нужно его элементам, а не всю доступную длину. Годится, когда элементов немного и они точно помещаются.
-
Primary — сделать этот список главным прокручиваемым виджетом в своём окружении. Влияет на то, как он уживается с другими прокручиваемыми областями. Подробнее.
-
Reverse — перевернуть порядок: вертикальный список начнётся снизу, горизонтальный — справа. Так обычно устроены чаты.

Перетаскивание элементов
Свойство Reorderable разрешает пользователю менять порядок элементов. В вебе и на десктопе FlutterFlow добавляет ручки для перетаскивания, на телефоне элемент берётся долгим нажатием.
Новый порядок сам по себе никуда не сохраняется. Записать его — задача действий на триггере On Reorder.
У Reorderable ListView потомки должны генерироваться из списка. На статичном наборе включение этой настройки приведёт к ошибке.
Ниже — как это настроить.
На переменной App State
- Заведите переменную App State со списком значений типа
Stringи выведите её в ListView. - Выделите ListView, откройте Properties Panel > ListView Properties и включите Reorderable.
- Откройте раздел Actions и перейдите в Action Flow Editor.
- Выберите триггер On Reorder — действия под ним выполняются после того, как пользователь отпустил перетащенный элемент.
- Создайте собственное действие, которое переставит элемент в исходном списке. Ему нужны три аргумента: сам список, Old Index и New Index. Старый и новый индексы берутся из Set Variable > Reorderable ListView.
- Код действия:
// Функция reorderItems возвращает Future со списком строк
// в новом порядке.
Future<List<String>> reorderItems(
List<String> list,
int oldIndex,
int newIndex,
) async {
// Если элемент двигают вниз, целевой индекс нужно уменьшить:
// после удаления элемента индексы сдвигаются.
if (oldIndex < newIndex) {
newIndex -= 1;
}
// Убираем элемент с прежнего места.
final item = list.removeAt(oldIndex);
// Вставляем на новое.
list.insert(newIndex, item);
// Возвращаем изменённый список.
return list;
}
- Список, который вернуло действие, запишите обратно действием Update App State.
На запросе к Firestore
Для элементов, пришедших из коллекции Firestore, порядок действий тот же, с несколькими отличиями.
Такой способ рассчитан на небольшие списки. На больших наборах он работает медленно, а частые записи в Firestore заметно увеличивают счёт за проект.
- Заведите в коллекции поле
order. - Отсортируйте запрос по этому полю.
- Отключите бесконечную прокрутку.
- Замените код собственного действия на такой:
Future reorderFirebaseItems(
List<PlaylistRecord> list,
int oldIndex,
int newIndex,
) async {
// Если элемент двигают вниз, корректируем newIndex.
if (oldIndex < newIndex) {
newIndex -= 1;
}
// Убираем элемент с прежнего места.
final PlaylistRecord item = list.removeAt(oldIndex);
// Вставляем на новое.
list.insert(newIndex, item);
// Собираем все операции Firestore в один batch.
final batch = FirebaseFirestore.instance.batch();
// Проходим по списку и обновляем поле order у каждого документа.
for (int i = 0; i < list.length; i++) {
final PlaylistRecord doc = list[i];
// Записываем в поле 'order' новый индекс элемента.
// Поле 'order' должно существовать в коллекции.
batch.update(doc.reference, {
'order': i
});
}
// Отправляем все операции разом.
return await batch.commit();
}
ListTile
ListTile — готовая строка списка: заголовок, подзаголовок и значки по краям. Из таких строк собирают меню, боковые панели и любые перечни с одинаковой структурой элементов. Собирать то же самое из Row и Column дольше, а выглядеть оно будет менее ровно.
ListTile подходит, если:
- элементы списка устроены одинаково;
- нужен рабочий интерфейс быстро, без тонкой настройки внешнего вида;
- важны отклик на нажатие и поддержка программ чтения с экрана — у ListTile они из коробки.
Если строка нестандартная, проще сделать свой компонент.
Title, Subtitle и Icon настраиваются в панели свойств.

Про настройку текста внутри — в документации виджета Text.
Превращение в SlidableListTile
ListTile можно превратить в свайпаемый: под строкой прячутся действия, которые открываются смахиванием влево — обычное «удалить» или «в архив».
Как включить и настроить действия:
GridView
GridView раскладывает элементы плиткой с прокруткой — галерея фотографий, каталог, игровое поле.
Как и ListView, сетка отрисовывает только видимые элементы. Настраиваются число колонок, промежутки, соотношение сторон плитки и направление прокрутки.

Добавление GridView и правка его свойств:
StaggeredView
GridView держит плитки одного размера, StaggeredView допускает разные. Второй нужен для раскладки в духе Pinterest, где высота плитки зависит от содержимого.

Дополнительные свойства
-
Shrink Wrap — по умолчанию GridView занимает всё место вдоль своей главной оси: при вертикальном направлении — всю высоту экрана, при горизонтальном — всю ширину. Shrink Wrap ограничивает его размером содержимого.
-
Primary — сделать сетку главным прокручиваемым виджетом. Нужно, когда прокручиваемая область на экране одна. Подробнее.
Смотрите ролик:
Бесконечная прокрутка
Бесконечная прокрутка догружает элементы по мере пролистывания. Сначала показывается ограниченный набор — 15 или 25 позиций, — а следующая порция подгружается ещё до того, как пользователь доберётся до конца. Пока она грузится, внизу крутится индикатор.

Выигрыш двойной: экран открывается быстрее, а данные, до которых не долистали, вообще не запрашиваются.
Источников может быть два:
Список из коллекции Firestore
Порядок такой:
- Запросите коллекцию на ListView, если запрос ещё не настроен.
- Выделите ListView и откройте раздел Backend Query в панели свойств.
- Найдите настроенный запрос и включите Enable Infinite Scroll.
- Вместе с ним включается Listen For Changes — обновление показанных элементов при изменении их данных. Добавление и удаление элементов оно не отслеживает. Если обновления в реальном времени не нужны, выключите эту настройку: каждое такое подключение стоит чтений.
- Элементы грузятся порциями — страницами. Размер страницы задаётся полем Page Size, по умолчанию 25. Первая страница подгружается, когда список появляется на экране, остальные — по мере прокрутки.
- Нажмите Save.

Список из вызова API
Здесь нужен эндпоинт, который поддерживает постраничную выдачу хотя бы через один параметр запроса — page, offset или limit.
Переменные пагинации
Когда в проекте есть постраничный вызов API и включена бесконечная прокрутка, в меню Set Variable появляются переменные пагинации. Их и подставляют в параметры запроса.

- Next Page Index — номер страницы. Начинается с
0и растёт на единицу по мере прокрутки, пока список не закончится. - # (Number of) Loaded Items — сколько элементов вернул постраничный вызов.
- Last Response — данные предыдущего ответа; из них можно взять курсор или ссылку на следующую порцию.
:::tip
Для параметров вроде limit, per_page или size задавайте Number of Loaded Items как Specific Value — например, 15 или 20.
Дальше — два шага.
1. Настроить постраничный вызов API
Постраничный API отдаёт данные порциями. Обычно за это отвечают параметры запроса: сколько элементов вернуть и с какого места начать.
Например, в https://reqres.in/api/users?per_page=20&page=1 параметр per_page просит 20 элементов, а page — первую страницу. Это пагинация по номеру страницы.
Как задаются параметры запроса у вызова API.
2. Передать переменную пагинации в вызов
Положите ListTile внутрь ListView и настройте список на данные постраничного вызова.
- Запросите API и выведите его данные.
- Подставьте переменную пагинации в нужный параметр запроса.
Свойство Primary
Когда Primary включено, прокручивать область можно даже если содержимое помещается целиком: оно слегка сдвинется и вернётся обратно. На iOS это заметно по характерному отскоку.
Если прокручиваемые виджеты вложены друг в друга, главным должен быть только один.
Как правило, главным делают внешний — тот, что занимает весь экран. Например, в связке Column > ListView главным остаётся Column, а ListView — нет.
Обновление жестом
Pull-to-refresh — обновление содержимого потягиванием списка вниз: пользователь тянет, отпускает, приложение запрашивает свежие данные.
Как включить:
- Выделите прокручиваемый виджет —
ListView,GridViewилиStaggeredView. - Откройте раздел Backend Query в панели свойств.
- Включите Enable Pull to Refresh.
Для документов Firestore сначала включите Single Time Query — иначе обновлять нечего, данные и так приходят в реальном времени.

Действие Scroll To
Действие Scroll To перематывает прокручиваемый виджет в начало или в конец.
На странице должен быть прокручиваемый виджет — ListView, StaggeredView или GridView — и в нём достаточно элементов, чтобы прокрутка вообще была возможна.
Порядок такой:
- Выделите виджет, с которого будет запускаться перемотка, — например, FloatingActionButton.
- Откройте Actions в панели свойств и нажмите + Add Action.
- Найдите действие Scroll To в группе Widget/UI Interactions.
- В Scrollable Widget to Control выберите нужный список на странице.
- Задайте Scroll To — Beginning или End.
- Укажите Duration в миллисекундах (
1000 ms= 1 секунда) — за это время закончится анимация перемотки.
Чем длиннее список, тем короче стоит делать длительность: иначе перемотка через тысячу позиций превращается в отдельное зрелище.
