Отображай списки из тысяч элементов без тормозов благодаря ListView.builder, ListTile и элементам, вынесенным в отдельные виджеты.
Открыть этот урок в KodokonНастоящее приложение почти всегда показывает списки: сообщения, товары, контакты. Уже знакомый тебе конструктор ListView(children: ...) строит все дочерние виджеты сразу - нормально для десяти элементов, катастрофа для тысячи. ListView.builder строит только элементы рядом с видимой областью, по мере необходимости, пока ты прокручиваешь.
import 'package:flutter/material.dart';
class ContactsScreen extends StatelessWidget {
const ContactsScreen({super.key, required this.names});
final List<String> names;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Contacts')),
body: ListView.builder(
itemCount: names.length,
itemBuilder: (context, index) {
return ListTile(
leading: const Icon(Icons.person),
title: Text(names[index]),
);
},
),
);
}
}Весь механизм держится на двух параметрах: itemCount объявляет общее количество элементов, а itemBuilder создаёт элемент по заданному индексу, только когда тот приближается к экрану. За содержимое каждой строки отвечает ListTile, твой союзник из Material: leading, title, subtitle и trailing задают структуру строки, а onTap добавляет чернильную волну при касании. Но как только элемент разрастается, не давай ему раздуваться внутри itemBuilder: вынеси его в отдельный виджет.
import 'package:flutter/material.dart';
class ContactTile extends StatelessWidget {
const ContactTile({
super.key,
required this.name,
required this.onTap,
});
final String name;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return ListTile(
leading: CircleAvatar(child: Text(name[0])),
title: Text(name),
trailing: const Icon(Icons.chevron_right),
onTap: onTap,
);
}
}Вынесенный элемент можно тестировать отдельно, переиспользовать на других экранах, и он делает itemBuilder тривиальным. Остаётся одна частая потребность: разделители. ListView.separated работает как builder, но с третьим параметром separatorBuilder, который вызывается между каждой парой элементов - после последнего элемента разделителя никогда не будет.
import 'package:flutter/material.dart';
class ContactList extends StatelessWidget {
const ContactList({super.key, required this.names});
final List<String> names;
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: names.length,
separatorBuilder: (context, index) {
return const Divider(height: 1);
},
itemBuilder: (context, index) {
return ContactTile(
name: names[index],
onTap: () => debugPrint(names[index]),
);
},
);
}
}