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

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 или таблицы Supabase

Бесконечная прокрутка включается для ListView, который берёт записи из коллекции Firestore или таблицы Supabase. FlutterFlow грузит записи страницами и запрашивает следующую, когда пользователь долистывает до конца.

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

  1. Выделите ListView и откройте раздел Backend Query в Properties Panel.
  2. Добавьте запрос к коллекции или таблице либо откройте уже настроенный:
    • для Firestore — Query Collection и List of Documents;
    • для Supabase — Supabase Query и List of Rows.
  3. Настройте фильтры, если они нужны.
  4. Настройте сортировку:
    • для Firestore — так, как нужно списку;
    • для Supabase — сначала убедитесь, что в импортированной схеме у таблицы есть первичный ключ. В секции Ordering добавьте сортировку по нему (например, по id) и выберите Increasing или Decreasing. Это даёт FlutterFlow устойчивый уникальный порядок при загрузке страниц — без него строки могут повторяться или пропадать.
  5. Включите Enable Infinite Scroll.
  6. Задайте Page Size — сколько записей грузится за одну страницу. По умолчанию 25.
  7. Нажмите Confirm.

Для запросов Firestore вместе с бесконечной прокруткой включается Listen For Changes: уже показанные документы обновляются, когда меняются их данные, но при создании или удалении документов элементы списка не появляются и не пропадают. Если обновления в реальном времени не нужны, выключите Listen For Changes — каждое такое подключение стоит чтений.

Firestore

Запрос к коллекции Firestore с включённой бесконечной прокруткой

Supabase

Запрос List of Rows к Supabase, отсортированный по первичному ключу, с включённой бесконечной прокруткой

Список из вызова 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 To — Beginning или End.
  6. Укажите Duration в миллисекундах (1000 ms = 1 секунда) — за это время закончится анимация перемотки.
совет

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

scroll-to-action

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

обновлено · перевод от

⌕ ESC