Ответ
В Flutter есть несколько виджетов для отображения списков, каждый со своими особенностями производительности и использования:
1. ListView — базовый виджет для вертикальных/горизонтальных списков:
// Простой список с заранее созданными детьми
ListView(
padding: EdgeInsets.all(16),
children: [
ListTile(title: Text('Item 1'), leading: Icon(Icons.star)),
ListTile(title: Text('Item 2'), leading: Icon(Icons.star)),
ListTile(title: Text('Item 3'), leading: Icon(Icons.star)),
],
)
// С разделителями
ListView.separated(
itemCount: 20,
separatorBuilder: (context, index) => Divider(height: 1, color: Colors.grey[300]),
itemBuilder: (context, index) => ListTile(
title: Text('Item $index'),
subtitle: Text('Subtitle $index'),
trailing: Icon(Icons.chevron_right),
),
)
2. ListView.builder — для динамических списков (ленивая загрузка):
final List<Product> products = [...]; // 1000+ элементов
ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ProductCard(
product: product,
onTap: () => _openProductDetails(product),
);
},
// Оптимизация производительности
addAutomaticKeepAlives: true, // Сохраняет состояние
addRepaintBoundaries: true, // Изолирует перерисовку
cacheExtent: 500, // Предзагрузка вне viewport
)
3. GridView — для отображения в виде сетки:
// Фиксированное количество колонок
GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 0.8, // Соотношение сторон
children: List.generate(20, (index) =>
Card(
child: Column(
children: [
Image.network('https://picsum.photos/200?random=$index'),
Text('Item $index'),
],
),
),
),
)
// Динамическая сетка с builder
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: MediaQuery.of(context).size.width > 600 ? 4 : 2,
childAspectRatio: 1.0,
),
itemCount: products.length,
itemBuilder: (context, index) => ProductGridItem(product: products[index]),
)
4. CustomScrollView + Slivers — для сложных кастомных списков:
CustomScrollView(
slivers: [
// Заголовок (прилипающий)
SliverAppBar(
title: Text('Products'),
floating: true,
snap: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.network('https://...', fit: BoxFit.cover),
),
),
// Фильтры (не прокручиваются)
SliverPersistentHeader(
delegate: _StickyHeaderDelegate(
child: FilterBar(onFilterChanged: _applyFilter),
),
pinned: true,
),
// Основной список продуктов
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ProductCard(product: filteredProducts[index]),
childCount: filteredProducts.length,
),
),
// Загрузка следующей страницы
if (_isLoadingMore)
SliverToBoxAdapter(
child: Center(child: CircularProgressIndicator()),
),
// Подвал
SliverToBoxAdapter(
child: Footer(),
),
],
)
5. ListView с различными типами элементов:
ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
// Разные типы элементов в одном списке
switch (item.type) {
case 'header':
return _buildHeader(item);
case 'product':
return _buildProductCard(item);
case 'banner':
return _buildBanner(item);
case 'divider':
return Divider(height: 24);
default:
return SizedBox.shrink();
}
},
)
6. Оптимизация производительности для больших списков:
// 1. Использование const конструкторов где возможно
ListView.builder(
itemBuilder: (context, index) => const ProductCard(
// const параметры
),
)
// 2. AutomaticKeepAliveClientMixin для сохранения состояния
class ProductCardState extends State<ProductCard>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true; // Сохранять состояние при скролле
@override
Widget build(BuildContext context) {
super.build(context); // Обязательный вызов
return ...;
}
}
// 3. Кэширование сложных вычислений
ListView.builder(
itemBuilder: (context, index) {
final product = products[index];
// Дорогие вычисления кэшируем
final formattedPrice = _priceCache.putIfAbsent(
product.id,
() => _formatPrice(product.price),
);
return ProductCard(price: formattedPrice);
},
)
7. Пакеты для специфичных задач:
flutter_staggered_grid_view— кастомные раскладки сеткиinfinite_scroll_pagination— пагинация с подгрузкойreorderables— перетаскивание элементов в спискеscrollable_positioned_list— прокрутка к конкретной позиции
В production-приложениях я чаще всего использую комбинацию: CustomScrollView с разными Slivers для сложных интерфейсов и ListView.builder/GridView.builder для ленивой загрузки больших наборов данных. Ключевое правило — всегда использовать builder-конструкторы для списков с переменным или большим количеством элементов, чтобы Flutter создавал виджеты только для видимой области.
Ответ 18+ 🔞
А, ну вот, Flutter и его списки, ебать мои старые костыли. Это как выбирать, на чём домой ехать: можно на метро, а можно на старой «копейке», которая бензина хавает как не в себя, но зато с душой. Сейчас разжую, что к чему, а то удивление пиздец, когда новички ListView с детьми на тысячу элементов пихают и потом орут, что приложение тормозит.
1. ListView — это как базовая шаурма, без изысков.
// Это когда ты заранее знаешь, что у тебя там три пункта меню и всё.
// Создаёт все виджеты сразу, даже те, что за экраном. Для длинных списков — пиздец.
ListView(
padding: EdgeInsets.all(16),
children: [
ListTile(title: Text('Item 1'), leading: Icon(Icons.star)),
ListTile(title: Text('Item 2'), leading: Icon(Icons.star)),
ListTile(title: Text('Item 3'), leading: Icon(Icons.star)),
],
)
// А это уже поинтереснее, с разделителями. Но опять же, `itemCount` маленький должен быть.
ListView.separated(
itemCount: 20,
separatorBuilder: (context, index) => Divider(height: 1, color: Colors.grey[300]),
itemBuilder: (context, index) => ListTile(
title: Text('Item $index'),
subtitle: Text('Subtitle $index'),
trailing: Icon(Icons.chevron_right),
),
)
Суть в чём: separated — это builder под капотом, так что он ленивый. Но если ты в children засунешь сотню виджетов, то приложение будет грузиться, будто на дворе 2002-й год и интернет через модем.
2. ListView.builder — твой главный друг, когда данных овердохуища.
final List<Product> products = [...]; // Допустим, тысяча товаров из апи
ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ProductCard(
product: product,
onTap: () => _openProductDetails(product),
);
},
// Вот эти штуки — маст хэв для перформанса, ёпта.
addAutomaticKeepAlives: true, // Чтобы состояние карточек не сбрасывалось, когда ты скролишь туда-сюда.
addRepaintBoundaries: true, // Чтобы перерисовывалась только нужная карточка, а не весь список.
cacheExtent: 500, // Он заранее создаст виджеты чуть выше и ниже видимой области, чтобы скролл был плавный.
)
Вот это уже серьёзно. Он создаёт виджеты только для тех элементов, которые на экране или вот-вот появятся. Остальные — похуй, их в памяти нет. Экономия — просто пизда рулю.
3. GridView — когда нужно вывести не в линию, а плиткой, как в маркете.
// Фиксированное число колонок. Просто и понятно.
GridView.count(
crossAxisCount: 2, // Две колонки, хоть тресни.
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 0.8, // Важно поиграть с этим значением, а то квадраты будут кривые.
children: List.generate(20, (index) =>
Card(
child: Column(
children: [
Image.network('https://picsum.photos/200?random=$index'),
Text('Item $index'),
],
),
),
),
)
// А это — ленивая версия, для динамических данных. Тоже builder.
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: MediaQuery.of(context).size.width > 600 ? 4 : 2, // Адаптивность, ёб твою мать!
childAspectRatio: 1.0,
),
itemCount: products.length,
itemBuilder: (context, index) => ProductGridItem(product: products[index]),
)
Запомни: GridView с children — для статики, GridView.builder — для всего остального. Иначе получишь хуй с горы в виде лагов.
4. CustomScrollView + Slivers — это высший пилотаж, для сложных, блядь, интерфейсов.
CustomScrollView(
slivers: [
// Это чтобы шапка красиво схлопывалась и выезжала.
SliverAppBar(
title: Text('Products'),
floating: true,
snap: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.network('https://...', fit: BoxFit.cover),
),
),
// А это чтобы панель фильтров прилипала сверху при скролле.
SliverPersistentHeader(
delegate: _StickyHeaderDelegate(
child: FilterBar(onFilterChanged: _applyFilter),
),
pinned: true,
),
// Сам список товаров. Обрати внимание — SliverList, а не просто ListView.
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ProductCard(product: filteredProducts[index]),
childCount: filteredProducts.length,
),
),
// Индикатор загрузки в самом низу, когда подгружаем новую страницу.
if (_isLoadingMore)
SliverToBoxAdapter(
child: Center(child: CircularProgressIndicator()),
),
// И какой-нибудь футер в конце.
SliverToBoxAdapter(
child: Footer(),
),
],
)
Выглядит страшно, но это самый мощный инструмент. Можно собрать экран с кучей разных скролящихся секций, которые ведут себя независимо. Волнение ебать, когда впервые настраиваешь.
5. Список с разными типами элементов — обычная история.
ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
// Просто свитч-кейс, ничего сложного.
switch (item.type) {
case 'header':
return _buildHeader(item);
case 'product':
return _buildProductCard(item);
case 'banner':
return _buildBanner(item);
case 'divider':
return Divider(height: 24);
default:
return SizedBox.shrink(); // На всякий пожарный.
}
},
)
Тут главное — логику в itemBuilder не засовывать тяжёлую, а то будет тормозить.
6. Оптимизация, или как не облажаться на больших списках.
// 1. Константы — твои лучшие друзья. Где можно, пиши const.
ListView.builder(
itemBuilder: (context, index) => const ProductCard( // Вот этот const
// ...параметры
),
)
// 2. Миксин для сохранения состояния. Например, чтобы видео в карточке не перезапускалось при скролле.
class ProductCardState extends State<ProductCard>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true; // Говорим: "Храни меня, мама!"
@override
Widget build(BuildContext context) {
super.build(context); // ЭТУ СТРОКУ ПРОПУСТИТЬ НЕЛЬЗЯ, иначе накроется медным тазом.
return ...;
}
}
// 3. Кэшируй всё, что дорого считается. Не надо на каждом фрейме цены форматировать.
ListView.builder(
itemBuilder: (context, index) {
final product = products[index];
final formattedPrice = _priceCache.putIfAbsent(
product.id,
() => _formatPrice(product.price), // Сложная функция форматирования.
);
return ProductCard(price: formattedPrice);
},
)
7. Пакеты, когда родных возможностей не хватает.
Тут доверия ебать ноль к левым библиотекам, но некоторые реально годные:
flutter_staggered_grid_view— если нужна сетка с элементами разной высоты, как в Pinterest.infinite_scroll_pagination— для пагинации с подгрузкой, чтоб не изобретать велосипед.reorderables— чтобы пользователь мог перетаскивать элементы в списке местами.scrollable_positioned_list— если надо программно проскроллить к 255-му элементу. Сам от себя охуеешь, когда попробуешь сделать это на чистом виджете.
Итог, чувак:
Для простого списка — ListView.builder.
Для сетки — GridView.builder.
Если интерфейс — это ёперный театр с шапкой, баннерами и прилипающими штуками — CustomScrollView и Slivers.
И никогда, слышишь, НИКОГДА не используй обычный ListView или GridView с children для динамических данных. Это прямой путь в ад с лагами и вылетами. Всё, теперь ты знаешь.