Kodokon kodokon.com

قوائم عالية الأداء باستخدام ListView.builder

اعرض قوائم من آلاف العناصر دون تباطؤ بفضل ListView.builder وListTile والعناصر المستخرجة في ويدجات خاصة بها.

8 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

يعرض التطبيق الواقعي دائمًا تقريبًا قوائم: رسائل، ومنتجات، وجهات اتصال. إن الباني ListView(children: ...) الذي تعرفه بالفعل يبني كل أبنائه دفعة واحدة - وهذا مناسب لعشرة عناصر، لكنه كارثي لألف عنصر. أما ListView.builder فلا يبني إلا العناصر القريبة من المنطقة المرئية، عند الطلب، أثناء تمريرك للقائمة.

DART
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]),
          );
        },
      ),
    );
  }
}
لا تُبنى إلا العناصر المرئية، حتى مع 10,000 اسم.

يحمل وسيطان الآلية بأكملها: يُصرِّح itemCount بالعدد الإجمالي للعناصر، ويُنتِج itemBuilder العنصر الموجود عند فهرس معيّن، فقط عندما يقترب من الشاشة. أما لمحتوى كل صف، فإن ListTile هو حليفك من Material: تُنظِّم leading وtitle وsubtitle وtrailing الصف، وتوفر onTap تموّج الحبر عند اللمس. لكن حالما يكبر عنصر، لا تدعه يتضخّم داخل itemBuilder: استخرجه في ويدجت خاص به.

DART
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، يُستدعى بين كل زوج من العناصر - ولا يأتي أبدًا فاصل بعد العنصر الأخير.

DART
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]),
        );
      },
    );
  }
}
يُدرج separatorBuilder فاصل Divider بين كل زوج من العناصر.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. لماذا تفضّل ListView.builder على ListView(children: ...) لقائمة من 500 عنصر؟
    • يرتّب العناصر تلقائيًا
    • لا يبني إلا العناصر المرئية، عند الطلب
    • يخزّن الصور مؤقتًا
    • يضيف الفواصل تلقائيًا
  2. أيّ بانٍ يُدرج ويدجت بين كل زوج من العناصر؟
    • ListView.divided
    • ListView.builder
    • ListView.separated
  3. ما فائدة استخراج عنصر قائمة في صنف ويدجت خاص به؟
    • إنه ضروري لكي يُترجَم ListView.builder
    • إنه يتيح const، ويعزل عمليات إعادة البناء، ويُسهّل إعادة الاستخدام
    • إنه يجعل القائمة قابلة للتمرير
    • إنه يوفّر عليك كتابة itemCount