RecyclerView والقوائم
RecyclerView والقوائم
كل تطبيق أندرويد حقيقي يعرض قوائم: خلاصة رسائل، كتالوج منتجات، سجل معاملات. المكوّن المصمَّم لهذه المهمة هو RecyclerView. حلّ محل ListView القديم لأنه يحلّ مشكلتين صعبتين في آن واحد: إنه يُعيد استخدام كائنات الواجهة التي تختفي عن الشاشة بدلاً من تدميرها وإعادة إنشائها، ولا يفرض أي قيود على كيفية ترتيب العناصر أو تحريكها. إن فهم آلية عمل RecyclerView — ولماذا بُني بهذه الطريقة — سيجعلك مطوّر أندرويد أكثر كفاءة في كل ميزة تبنيها.
لماذا وُجد RecyclerView
تخيّل عرض 10,000 جهة اتصال. إنشاء 10,000 كائن View دفعة واحدة سيستهلك مئات الميغابايت من الذاكرة ويُجمّد الواجهة. يحلّ RecyclerView هذه المشكلة بالحفاظ على مجموعة صغيرة من كائنات الواجهة — أكثر بقليل من عدد العناصر المرئية على الشاشة — وإعادة تدويرها مع تمرير المستخدم. عندما تختفي صف من أعلى الشاشة، يُفصل عنصره عن النافذة ويُعاد إلى المجموعة. حين يحتاج صف جديد للظهور في الأسفل، يُسترجع عنصر من المجموعة وتُكتب بياناته الجديدة عليه ثم يُربط بالنافذة.
RecyclerView من كائنات الواجهة أكثر مما يحتاجه لملء الشاشة. قد يحتوي مجموعة البيانات على مليون عنصر؛ بصمة الذاكرة لواجهة القائمة تظل ثابتة.
المعمارية ذات الثلاثة أجزاء
صُمِّم RecyclerView عن قصد ليكون مقسَّمًا إلى ثلاثة كائنات متعاونة، لكل منها مسؤولية واحدة:
- RecyclerView — هو
ViewGroupالذي يوضع في ملف تصميم XML. يدير مجموعة العناصر ويعالج أحداث اللمس ويُنسّق التمرير. - LayoutManager — يقرر أين توضع العناصر. يمنحك
LinearLayoutManagerقائمة رأسية أو أفقية، وGridLayoutManagerشبكة، وStaggeredGridLayoutManagerصفوفًا غير متساوية بأسلوب Pinterest. - Adapter — يربط بياناتك بمجموعة العناصر. يُنشئ حاملات عرض جديدة حين تكون المجموعة فارغة، ويربط بيانات جديدة بحاملة عرض معادة الاستخدام حين يحتاجها النظام.
إضافة RecyclerView إلى التصميم
أضف أولاً الاعتماد إلى build.gradle (وحدة التطبيق). في المشاريع الحديثة يصل عادةً عبر appcompat أو material، لكن يمكنك تصريحه مباشرةً:
ثم ضعه في تصميم النشاط:
تعريف تصميم العنصر
كل صف يحتاج ملف تصميم خاص به. أنشئ res/layout/item_contact.xml:
فئة النموذج
كائن Java بسيط يحمل بيانات صف واحد:
بناء المحوّل وحامل العرض
هنا يقع العمل الحقيقي. للـAdapter ثلاث مسؤوليات تُعبَّر عنها بثلاث دوال يجب تجاوزها:
onCreateViewHolder— ينفّخ تصميم العنصر ويلفّه فيViewHolder. يُستدعى فقط حين لا تحتوي المجموعة على حاملة معادة الاستخدام.onBindViewHolder— يأخذ حاملة معادة الاستخدام (أو حديثة الإنشاء) ويملؤها ببيانات موضع معيّن. يُستدعى في كل مرة يظهر فيها صف على الشاشة.getItemCount— يُخبرRecyclerViewبعدد العناصر الموجودة.
الـViewHolder هو فئة داخلية تُخزّن مؤقتًا مراجع View لصف واحد. بدونها، سيضطر كل استدعاء لـonBindViewHolder إلى تنفيذ findViewById على العنصر الجذر — وهو اجتياز شجرة مكلف — عند كل حدث تمرير.
onBindViewHolder سريعًا. يعمل على خيط الواجهة ويُستدعى لكل صف يدخل نطاق الرؤية أثناء التمرير. لا استدعاءات شبكة، ولا قراءة قرص، ولا حسابات معقدة هنا — فقط قراءة بيانات واستدعاءات من نوع setText وsetImageBitmap. أي شيء أثقل من ذلك يجب أن يُنفَّذ في خيط خلفي قبل هذه النقطة.
ربط كل شيء معًا في النشاط
ثلاثة أسطر تقوم بكل العمل: تعيين LayoutManager (قائمة رأسية افتراضيًا)، وإنشاء المحوّل بالبيانات، وتعيينه. سيسأل RecyclerView فورًا المحوّل عن عدد العناصر ويبدأ في نفخ العناصر وربطها حسب الحاجة.
تحديث القائمة بكفاءة
استدعاء notifyDataSetChanged() يُجبر على إعادة ربط كل الصفوف المرئية — يعمل، لكنه يتخطى تحريكات تغيير العناصر ومُسرف. افضّل دوال الإشعار المحدَّدة حين تعلم ما الذي تغيّر بالضبط:
onBindViewHolder على خيط الواجهة. الكتابة المتزامنة من خيط آخر ستسبب تعطلاً أو تُنتج بيانات بصرية قديمة. نفّذ دائمًا تعديلات القائمة على الخيط الرئيسي، أو استخدم DiffUtil (يُغطّى في الدرس القادم) الذي يُسلّم عملية المبادلة النهائية إلى الخيط الرئيسي بأمان.
معالجة النقر على العناصر
على عكس ListView، لا يمتلك RecyclerView مستمع نقر مدمجًا. النمط الاصطلاحي هو تمرير واجهة رد نداء إلى المحوّل:
مرّر lambda من النشاط: new ContactAdapter(contacts, contact -> openDetail(contact)). هذا يُبقي المحوّل خاليًا من أي معرفة بالتنقل أو منطق الأعمال.
الخلاصة
يُبنى RecyclerView على ثلاثة كائنات تعمل معًا: المكوّن نفسه، وLayoutManager الذي يضع العناصر، وAdapter الذي يُنشئها ويربطها. نمط ViewHolder إلزامي — يُخزّن عمليات البحث findViewById كي يبقى onBindViewHolder سريعًا أثناء التمرير. هيكلة معالجة النقر كواجهة رد نداء تُبقي المحوّل مُركّزًا وقابلاً للاختبار. في الدرس القادم ستستبدل القائمة الثابتة ببيانات حية مجلوبة من قاعدة بيانات أو شبكة، وستتعلّم كيف يحسب DiffUtil أدنى التحديثات تلقائيًا.