اعرض قوائم من آلاف العناصر دون تباطؤ بفضل 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]),
);
},
);
}
}