Списки: ListView и ListView.builder
ListView показывает прокручиваемый список, а ListView.builder рисует элементы лениво — только видимые.
Суть:
ListViewподходит для короткого статичного списка, аListView.builder— для длинных или бесконечных данных: он создаёт виджеты по мере прокрутки, экономя память.
У ленивых списков есть близкие родственники для двумерных раскладок — GridView.builder для сеток и CustomScrollView со slivers для сложных прокручиваемых композиций, где шапка схлопывается при прокрутке. Все они разделяют ту же идею ленивого построения. Освоив принцип itemBuilder на ListView, вы практически бесплатно понимаете и эти более мощные инструменты, потому что контракт у них одинаковый.
Ленты, чаты, каталоги — везде списки, которые не помещаются на экран и должны прокручиваться. Простой ListView с явным children хорош для нескольких элементов. Но если их сотни или они приходят из сети, создавать все сразу расточительно — экран всё равно покажет лишь часть. Здесь нужен ListView.builder.
// короткий статичный список
ListView(
children: const [
ListTile(title: Text('Первый')),
ListTile(title: Text('Второй')),
ListTile(title: Text('Третий')),
],
)
// длинный список — ленивая отрисовка
final items = List.generate(1000, (i) => 'Элемент $i');
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
leading: const Icon(Icons.label),
title: Text(items[index]),
);
},
)
itemBuilder — это функция, которую Flutter вызывает для каждого видимого элемента, передавая его index. Вы возвращаете виджет строки. itemCount сообщает общее число элементов.
Как работает ленивая отрисовка под капотом
Обычный ListView строит все дочерние виджеты сразу. ListView.builder вызывает itemBuilder только для элементов, попадающих в видимую область плюс небольшой запас. Когда вы прокручиваете, верхние элементы уходят за экран и утилизируются, а новые внизу создаются. Поэтому список из миллиона строк потребляет столько же памяти, сколько помещается на экране.
весь список данных (1000 элементов)
[0][1][2] ... [498][499][500][501] ... [999]
^^^^^^^^^^^^^^^^^
| видимая |
| область +/- | <- только эти построены
^^^^^^^^^^^^^^^^^
прокрутка вниз --> верхние утилизируются,
нижние строятся builder'ом
# Модель ListView.builder: строим только видимые элементы
items = [f'Элемент {i}' for i in range(1000)]
def build_visible(items, scroll_offset, viewport=5):
# строим только окно видимых элементов, а не все 1000
start = scroll_offset
end = min(len(items), scroll_offset + viewport)
built = [items[i] for i in range(start, end)]
print(f'построено виджетов: {len(built)} из {len(items)}')
return built
print(build_visible(items, scroll_offset=0))
print(build_visible(items, scroll_offset=498)) # прокрутили вниз
Частые ошибки
- Использовать обычный
ListViewс тысячами элементов — это создаёт их все сразу и тормозит. Берите.builder. - Класть
ListViewвColumnбез ограничения высоты — список не знает, сколько места занять. НуженExpanded. - Забыть
itemCount— список не будет знать, где остановиться.
Best practices
- Для длинных или сетевых данных всегда используйте
ListView.builder. - Для разделителей между элементами есть
ListView.separated. - Добавляйте уникальные
keyэлементам, если список меняется (вставки/удаления), — Flutter точнее переиспользует виджеты.
Тонкий, но важный момент — ключи (Key). Когда список меняется (элементы добавляются, удаляются, переставляются), Flutter сопоставляет старые и новые виджеты, и без уникальных ключей он может перепутать, какой элемент какой, что приводит к странностям вроде сохранённого состояния не в той строке. Присваивая каждому элементу ValueKey по его идентификатору, вы помогаете Flutter точно отслеживать элементы при изменениях списка.
Итог: ListView.builder — ключ к производительным спискам любой длины. Понимание ленивой отрисовки объясняет, почему Flutter-приложения остаются плавными даже на огромных данных. Дальше украсим элементы карточками и иконками.