Списки и сетки

ListView и GridView выводят наборы элементов — списком и плиткой. Оба рассчитаны на данные, которых заранее неизвестно сколько: картинки, тексты, карточки товаров.

ListView

ListView — прокручиваемый список виджетов, выстроенных в линию: по вертикали или по горизонтали.

Главное его свойство — отрисовываются только видимые элементы. Список на тысячу позиций стоит примерно столько же, сколько список на десять.

Настройки такие:

Направление

Axis задаёт ориентацию списка: Vertical или Horizontal.

listview-axis.png

Расстояния

  • Items Spacing — промежуток между соседними элементами в пикселях.
совет
Items Spacing вместо отступов

В списках, собранных вручную, задавайте Items Spacing на родителе, а не отступы у каждого элемента: так промежутки гарантированно одинаковые.

  • Apply to Start & End — добавить такой же отступ в начале и в конце списка.

  • Start Spacing и End Spacing — отдельные значения для начала и конца, независимо от промежутков между элементами.

Дополнительные свойства

  • Shrink Wrap — список занимает ровно столько места, сколько нужно его элементам, а не всю доступную длину. Годится, когда элементов немного и они точно помещаются.

  • Primary — сделать этот список главным прокручиваемым виджетом в своём окружении. Влияет на то, как он уживается с другими прокручиваемыми областями. Подробнее.

  • Reverse — перевернуть порядок: вертикальный список начнётся снизу, горизонтальный — справа. Так обычно устроены чаты.

listview-reverse.png

Перетаскивание элементов

Свойство Reorderable разрешает пользователю менять порядок элементов. В вебе и на десктопе FlutterFlow добавляет ручки для перетаскивания, на телефоне элемент берётся долгим нажатием.

инфо
Учтите

Новый порядок сам по себе никуда не сохраняется. Записать его — задача действий на триггере On Reorder.

опасно
Только динамические потомки

У Reorderable ListView потомки должны генерироваться из списка. На статичном наборе включение этой настройки приведёт к ошибке.

Ниже — как это настроить.

На переменной App State

  1. Заведите переменную App State со списком значений типа String и выведите её в ListView.
  2. Выделите ListView, откройте Properties Panel > ListView Properties и включите Reorderable.
  3. Откройте раздел Actions и перейдите в Action Flow Editor.
  4. Выберите триггер On Reorder — действия под ним выполняются после того, как пользователь отпустил перетащенный элемент.
  5. Создайте собственное действие, которое переставит элемент в исходном списке. Ему нужны три аргумента: сам список, Old Index и New Index. Старый и новый индексы берутся из Set Variable > Reorderable ListView.
  6. Код действия:
// Функция 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;
}
  1. Список, который вернуло действие, запишите обратно действием Update App State.

На запросе к Firestore

Для элементов, пришедших из коллекции Firestore, порядок действий тот же, с несколькими отличиями.

опасно
Осторожно

Такой способ рассчитан на небольшие списки. На больших наборах он работает медленно, а частые записи в Firestore заметно увеличивают счёт за проект.

  1. Заведите в коллекции поле order.
  2. Отсортируйте запрос по этому полю.
  3. Отключите бесконечную прокрутку.
  4. Замените код собственного действия на такой:
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 подходит, если:

  • элементы списка устроены одинаково;
  • нужен рабочий интерфейс быстро, без тонкой настройки внешнего вида;
  • важны отклик на нажатие и поддержка программ чтения с экрана — у ListTile они из коробки.

Если строка нестандартная, проще сделать свой компонент.

Title, Subtitle и Icon настраиваются в панели свойств.

list-tile.png

инфо

Про настройку текста внутри — в документации виджета Text.

Превращение в SlidableListTile

ListTile можно превратить в свайпаемый: под строкой прячутся действия, которые открываются смахиванием влево — обычное «удалить» или «в архив».

Как включить и настроить действия:

GridView

GridView раскладывает элементы плиткой с прокруткой — галерея фотографий, каталог, игровое поле.

Как и ListView, сетка отрисовывает только видимые элементы. Настраиваются число колонок, промежутки, соотношение сторон плитки и направление прокрутки.

gridview.png

Добавление GridView и правка его свойств:

StaggeredView

совет
GridView или StaggeredView

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

staggeredView

Дополнительные свойства

  • Shrink Wrap — по умолчанию GridView занимает всё место вдоль своей главной оси: при вертикальном направлении — всю высоту экрана, при горизонтальном — всю ширину. Shrink Wrap ограничивает его размером содержимого.

  • Primary — сделать сетку главным прокручиваемым виджетом. Нужно, когда прокручиваемая область на экране одна. Подробнее.

совет
Видеоразбор

Смотрите ролик:

:::

Бесконечная прокрутка

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

infinite-scroll-behind-the-scene

Выигрыш двойной: экран открывается быстрее, а данные, до которых не долистали, вообще не запрашиваются.

Источников может быть два:

Список из коллекции Firestore

Порядок такой:

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

enable-infinite-list

Список из вызова API

Здесь нужен эндпоинт, который поддерживает постраничную выдачу хотя бы через один параметр запроса — page, offset или limit.

Переменные пагинации

Когда в проекте есть постраничный вызов API и включена бесконечная прокрутка, в меню Set Variable появляются переменные пагинации. Их и подставляют в параметры запроса.

pagination-variables

  1. Next Page Index — номер страницы. Начинается с 0 и растёт на единицу по мере прокрутки, пока список не закончится.
  2. # (Number of) Loaded Items — сколько элементов вернул постраничный вызов.
  3. 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 и настройте список на данные постраничного вызова.

  1. Запросите API и выведите его данные.
  2. Подставьте переменную пагинации в нужный параметр запроса.

Свойство Primary

Когда Primary включено, прокручивать область можно даже если содержимое помещается целиком: оно слегка сдвинется и вернётся обратно. На iOS это заметно по характерному отскоку.

совет

Если прокручиваемые виджеты вложены друг в друга, главным должен быть только один.

Как правило, главным делают внешний — тот, что занимает весь экран. Например, в связке Column > ListView главным остаётся Column, а ListView — нет.

Обновление жестом

Pull-to-refresh — обновление содержимого потягиванием списка вниз: пользователь тянет, отпускает, приложение запрашивает свежие данные.

Как включить:

  1. Выделите прокручиваемый виджет — ListView, GridView или StaggeredView.
  2. Откройте раздел Backend Query в панели свойств.
  3. Включите Enable Pull to Refresh.

инфо

Для документов Firestore сначала включите Single Time Query — иначе обновлять нечего, данные и так приходят в реальном времени.

enable-infinite-list

Действие Scroll To

Действие Scroll To перематывает прокручиваемый виджет в начало или в конец.

инфо

На странице должен быть прокручиваемый виджет — ListView, StaggeredView или GridView — и в нём достаточно элементов, чтобы прокрутка вообще была возможна.

Порядок такой:

  1. Выделите виджет, с которого будет запускаться перемотка, — например, FloatingActionButton.
  2. Откройте Actions в панели свойств и нажмите + Add Action.
  3. Найдите действие Scroll To в группе Widget/UI Interactions.
  4. В Scrollable Widget to Control выберите нужный список на странице.
  5. Задайте Scroll ToBeginning или End.
  6. Укажите Duration в миллисекундах (1000 ms = 1 секунда) — за это время закончится анимация перемотки.
совет

Чем длиннее список, тем короче стоит делать длительность: иначе перемотка через тысячу позиций превращается в отдельное зрелище.

scroll-to-action

перевод официальной документации FlutterFlow

обновлено

ESC