Я исследую разницу в производительности между ListView и ListView.builder во Flutter. Теория гласит, что ListView создает все виджеты сразу, а ListView.builder создает, когда он виден.
Я проверил с помощью Devtool, что это правда. ListView создает все виджеты и сохраняет их в памяти, но когда я прокручиваю ListView, виджеты удаляются и создаются, когда они становятся видимыми/невидимыми, я имею в виду, когда они находятся на экране или нет.
Как это возможно? Если все виджеты в ListView уже находятся в памяти, почему они создаются и удаляются?
Вы можете проверить это поведение с помощью этого кода:
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: PageOne());
}
}
class PageOne extends StatelessWidget {
const PageOne({super.key});
@override
Widget build(BuildContext context) {
final items = List.generate(500000, (index) => Item(value: index));
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ThirdPage(items: items),
)),
child: const Text('ListView'),
),
],
),
),
);
}
}
class ThirdPage extends StatelessWidget {
const ThirdPage({super.key, required this.items});
final List<Item> items;
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: items.map((e) => _Item(item: e)).toList(),
),
);
}
}
class _Item extends StatefulWidget {
const _Item({required this.item});
final Item item;
@override
State<_Item> createState() => _ItemState();
}
class _ItemState extends State<_Item> {
@override
void initState() {
super.initState();
print('Create widget ${widget.item.value}');
}
@override
void dispose() {
print('Delete widget ${widget.item.value}');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text('${widget.item.value}');
}
}
Это неправда. Вы можете проверить это с помощью кода выше.
@Manuel Единственная ситуация, с которой я могу с вами согласиться, - это «когда этот listview
создается на очень-очень длинном экране», в то время как пользователь прокручивает вверх и вниз, предположим, что он расположен внизу этого экрана. поэтому, когда вы находитесь в верхней части этого экрана. он может быть удален и воссоздан при выходе из строя.
вы путаете виджеты с состояниями - при использовании ListView
приходится передавать список с уже созданными виджетами, при ListView.builder
они создаются по требованию
@Мануэль, очень поучительно. Кстати, ваш пример кода будет более понятным, если вы измените сообщения: «Создать виджет» -> «Состояние инициализации», «Удалить виджет» -> «Удалить состояние». Вы также можете напечатать сообщение в build
: «Виджет здания: ...». А можно было бы добавить класс Item
.
Вы наблюдаете, что когда вы просматриваете список элементов и прокручиваете его вверх и вниз, последовательность вызова initState
, build
и dispose
одинакова, если вы используете ListView
или ListView.builder
.
Преимущество использования ListView.builder заключается в том, что виджеты создаются по требованию, когда виджет появляется в поле зрения. Под созданным я подразумеваю вызов конструктора и создание экземпляра.
Я расширил ваш пример и добавил одну страницу, используя ListView
, и другую страницу, используя ListView.builder
. Кроме того, я добавил статическую переменную
ItemWidget.count
, который увеличивается в теле конструктора и сохраняет количество созданных экземпляров.
import 'package:flutter/material.dart';
class Item {
Item({required this.index});
final int index;
}
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: PageOne());
}
}
class PageOne extends StatelessWidget {
const PageOne({super.key});
@override
Widget build(BuildContext context) {
final items = List.generate(25000, (index) => Item(index: index));
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListViewPage(items: items),
)),
child: const Text('ListView'),
),
ElevatedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListViewBuilderPage(items: items),
)),
child: const Text('ListViewBuilder'),
),
],
),
),
);
}
}
class ListViewPage extends StatelessWidget {
const ListViewPage({super.key, required this.items});
final List<Item> items;
@override
Widget build(BuildContext context) {
final children = items.map((e) => ItemWidget(item: e)).toList();
final listView = ListView(children: children);
return Scaffold(
body: listView,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.navigate_before),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PageOne(),
)),
),
);
}
}
class ListViewBuilderPage extends StatelessWidget {
const ListViewBuilderPage({super.key, required this.items});
final List<Item> items;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.navigate_before),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PageOne(),
)),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(item: items[index]),
),
);
}
}
class ItemWidget extends StatefulWidget {
ItemWidget({super.key, required this.item}) {
++_count;
}
final Item item;
static int _count = 0;
static int get count => _count;
@override
State<ItemWidget> createState() => _ItemWidgetState();
}
class _ItemWidgetState extends State<ItemWidget> {
@override
void initState() {
super.initState();
debugPrint(
'Init state ${widget.item.index} => count: ${ItemWidget.count}');
}
@override
void dispose() {
debugPrint(
'Dispose state ${widget.item.index} => count: ${ItemWidget.count}');
super.dispose();
}
@override
Widget build(BuildContext context) {
debugPrint(
'Build widget: ${widget.item.index} => count: ${ItemWidget.count}');
return Text('widget index: ${widget.item.index} => count: ${ItemWidget.count}');
}
}
Перезапускаем приложение и просматриваем страницу ListViewPage
, отображается следующий список:
widget index: 0 => count: 25000
widget index: 1 => count: 25000
widget index: 2 => count: 25000
...
Обратите внимание, что конструктор уже вызывался 25 000 раз.
Перезапускаем приложение и просматриваем страницу ListViewBuilderPage
, отображается следующий список:
widget index: 0 => count: 1
widget index: 1 => count: 2
widget index: 2 => count: 3
...
В этом случае ItemWidget
создаются по запросу.
Спасибо за пример и ответ. Я провел еще несколько проверок и пришел к следующему выводу: виджеты создаются сразу, но состояние создается по требованию в ListView. Мое замешательство возникло из-за такого поведения. Чего я не понимаю, так это почему состояние создается по требованию в ListView, а Stateful/StatelessWidget создаются сразу.
при использовании
ListView
все дочерние элементы создаются сразу и никогда не удаляются, приListView.builder
они создаются/удаляются только тогда, когда они видимы/скрыты