Android RecyclerView: ما هو، تعلم باستخدام أمثلة بسيطة

⚡ ملخص ذكي

RecyclerView هو برنامج متطور Android أداة تعرض مجموعات بيانات كبيرة قابلة للتمرير بكفاءة من خلال إعادة استخدام عدد محدود من طرق العرض. وتعتمد على مُهايئ، ومدير تخطيط، وحامل عرض لربط مجموعات البيانات التي تتغير أثناء التشغيل.

  • 📱 ما هو : RecyclerView هو بديل مرن لـ ListView و GridView لعرض مجموعات البيانات الكبيرة.
  • 🧱 طرق العرض ومجموعات العرض: Android يرسم الواجهة باستخدام فئات العرض ومجموعة العرض، مع تحكم LayoutParams في الحجم والموضع.
  • 🔄 محول: يقوم نمط المحول بتحويل مصفوفة البيانات إلى ViewHolders التي يمكن لـ RecyclerView قبولها.
  • 📐 مدير التخطيط: يقوم مدير التخطيط بترتيب العناصر بشكل خطي أو في شبكة ويشير إلى متى يمكن إعادة استخدام طرق العرض.
  • ♻️ إعادة التدوير: يتم إعادة استخدام المشاهدات خارج الشاشة بدلاً من إعادة إنشائها، مما يوفر الذاكرة ويحافظ على سلاسة التمرير.
  • 🛠️ خطوات البناء: إضافة Gradle التبعية، قم بإنشاء تخطيط عنصر، و ViewHolder، ومحول، و LayoutManager.

Android عرض إعادة التدوير

ما هو RecyclerView في Android?

استخدم عرض إعادة التدوير يُعد RecyclerView نسخةً أكثر مرونةً وتطورًا من GridView وListView. وهو عبارة عن حاوية لعرض مجموعات البيانات الكبيرة التي يمكن تصفحها بكفاءة مع الحفاظ على عدد محدود من طرق العرض. يمكنك استخدام عنصر واجهة المستخدم RecyclerView عندما يكون لديك مجموعات بيانات تتغير عناصرها أثناء التشغيل بناءً على حدث شبكي أو إجراء من المستخدم.

المشاهدات

استخدم Android تستخدم المنصة فئتي العرض (View) ومجموعة العرض (ViewGroup) لرسم العناصر على الشاشة. هذه الفئات هيtracيتم توسيع نطاق استخدام كل من TextView و EditText ليشمل تطبيقات مختلفة تناسب حالات الاستخدام المختلفة. على سبيل المثال، يقتصر دور TextView على عرض محتوى نصي على الشاشة. أما EditText، فهو مشتق من نفس فئة View ويضيف وظائف إضافية لتمكين المستخدم من إدخال البيانات.

من الممكن إنشاء طرق عرض مخصصة لتحقيق مزيد من المرونة عند التطويرping واجهات المستخدم. يوفر صنف العرض طرقًا يمكننا تجاوزها للرسم على الشاشة، ووسيلة لتمرير معلمات مثل العرض والارتفاع، وسماتنا المخصصة التي نرغب في إضافتها إلى العرض لجعله يتصرف كما نريد.

عرض المجموعات

تُعدّ فئة ViewGroup نوعًا من أنواع View، ولكن على عكس فئة View البسيطة التي تقتصر وظيفتها على العرض فقط، تُمكّننا ViewGroup من وضع عدة عروض في عرض واحد، يُمكننا الرجوع إليه كوحدة واحدة. في هذه الحالة، يُطلق على العرض المُنشأ في المستوى الأعلى، والذي نضيف إليه عروضًا بسيطة أخرى، اسم "العرض الأب"، بينما تُسمى العروض المُضافة داخله "العروض الأبناء".

يمكننا أن نتخيل العرض كمصفوفة، ومجموعة العروض كمصفوفة من المصفوفات. وبما أن مصفوفة المصفوفات هي مصفوفة بحد ذاتها، يمكننا أن نرى كيف يمكن التعامل مع مجموعة العروض كعرض.

var arr1 = [1,2,3] //imagine a simple View as an Array
//we can imagine this as a NumberTextView which doesn't really exist
//but we could imagine there's one that makes it easy to use numbers
var arr2 = ["a","b","c"] // We can imagine this as another simple view

var nestedArr = [arr1,arr2] //in our analogy, we can now group views
//together and the structure that would hold that would be what we call the ViewGroup

تتيح لنا مجموعة العرض (ViewGroup) أيضًا تحديد كيفية تنظيم العناصر الفرعية داخل العرض، على سبيل المثال، ما إذا كانت مرتبة عموديًا أو أفقيًا. يمكننا تطبيق قواعد مختلفة للتفاعل داخل العرض. على سبيل المثال، يجب أن تكون المسافة بين عناصر TextView المتتالية 12dp، بينما يجب أن تكون المسافة بين عنصر ImageView متبوعًا بعنصر TextView 5dp.

سيكون هذا هو الحال لو كنا نطورping إنشاء مجموعة العرض الخاصة بنا من الصفر. لتسهيل هذه التكوينات، Android يوفر فئة تسمى LayoutParams، والتي يمكننا استخدامها لإدخال هذه التكوينات.

استخدم Android توثيق يُوفّر هذا بعض المعلمات الافتراضية التي نُطبّقها عند إعداد ViewGroup الخاص بنا. تتعلّق بعض المعلمات الشائعة بالعرض والارتفاع والهامش. بشكل افتراضي، تأتي هذه الإعدادات بالبنية التالية: android:layout_height للارتفاع و android:layout_width للعرض. في هذا السياق، عند إنشاء ViewGroup، يُمكنك إنشاء LayoutParams إضافية تُحدّد طريقة عمل ViewGroup التي تُريدها.

Android يأتي مزودًا بعناصر عرض ومجموعات عرض افتراضية يمكننا استخدامها لإنجاز العديد من المهام الشائعة. أحد الأمثلة التي ذكرناها هو TextView، وهو عنصر عرض بسيط يأتي بخصائص قابلة للتخصيص مثل الارتفاع والعرض وحجم النص. لدينا أيضًا ImageView لعرض الصور، وEditText، كما ذكرنا، بالإضافة إلى العديد من العناصر الأخرى. Android يحتوي أيضًا على ViewGroups مخصصة يمكننا إضافة طرق عرضنا إليها والحصول على السلوك المتوقع.

التخطيط الخطي

يُتيح لنا LinearLayout إضافة عناصر العرض إليه. يحتوي LinearLayout على خاصية التوجيه التي تُحدد كيفية ترتيبه على الشاشة. كما يحتوي على LinearLayout.LayoutParams التي تُحدد قواعد عرض العناصر بداخله. على سبيل المثال، تعمل الخاصية android:center_horizontal على توسيط العناصر على المحور الأفقي، بينما تعمل الخاصية android:center_vertical على توسيط محتوى العرض على المحور الرأسي.

إليكم بعض الصور التوضيحية لفهم عملية التوسيط. لنفترض أن هذا عنصر TextView بسيط داخل مساحة 200 بكسل × 200 بكسل؛ ستجعل خصائص التوسيط سلوكه كما يلي.

أندرويد:مركز أفقي

المحتوى المتمركز أفقيًا

المحتوى المتمركز أفقيًا

أندرويد:مركز عمودي

محتوى متمركز عموديًا

محتوى متمركز عموديًا

أندرويد:المركز

محتوى مركزي

محتوى مركزي

المكونات الأساسية لبرنامج RecyclerView

المكونات الأساسية لبرنامج RecyclerView

المكونات الأساسية لبرنامج RecyclerView

فيما يلي المكونات المهمة لـ RecyclerView:

RecyclerView.Adapter

الأساس رقم 1 - نمط المحول

المحول هو جهاز يحول خصائص نظام أو جهاز ما إلى خصائص نظام أو جهاز آخر غير متوافق. بعض المحولات تُعدّل خصائص الإشارة أو الطاقة، بينما يقوم البعض الآخر ببساطة بتكييف الشكل المادي لموصل ما ليتوافق مع موصل آخر.

مثال بسيط من الحياة الواقعية يوضح فائدة المحول هو عندما نحتاج إلى توصيل أجهزة ببعضها، لكن منافذ التوصيل فيها غير متوافقة. قد يحدث هذا عند زيارة بلد يستخدم أنواعًا مختلفة من المقابس. إذا كنت تحمل شاحن هاتفك أو حاسوبك المحمول، فسيكون من المستحيل توصيله بمنافذ الطاقة. لكنك لن تستسلم، بل ستستخدم محولًا يربط بين منفذ الطاقة والشاحن، مما يتيح لك شحنه.

هذا هو الحال في البرمجة عندما نريد ربط بنيتين من البيانات معًا لإنجاز مهمة ما، ولكن منافذهما الافتراضية لا تملك طريقة للتواصل مع بعضها البعض.

سنستخدم مثالاً بسيطاً لجهاز وشاحن. سيكون لدينا نوعان من الشواحن، أحدهما أمريكي والآخر بريطاني.

class AmericanCharger() {
    var chargingPower = 10
}
class BritishCharger(){
    var chargingPower = 5
}

سنقوم بعد ذلك بإنشاء جهازين.

class AmericanDevice()
class BritishDevice()

على سبيل المثال، يمكننا بعد ذلك إنشاء بعض مثيلات الأجهزة للعب معها.

var myAmericanPhone = new AmericanDevice()
var myBritishPhone = new BritishDevice()

سنقدم بعد ذلك مفهوم الشحن لكلا الجهازين من خلال إضافة دالة في الجهازين تُسمى charge(). تأخذ هذه الدالة الشاحن الخاص بكل جهاز وتقوم بعملية الشحن بناءً عليه.

sealed trait Device
class AmericanDevice : Device{
    fun charge(charger:AmericanCharger){
        //Do some American charging
    }
}
class BritishDevice: Device{
    fun charge(charger:BritishCharger){
        //Do some British charging
    }
}

في هذه الحالة، وبناءً على قياسنا، سنحتاج لسبب أو لآخر إلى استخدام شاحن بريطاني عند استخدام جهاز أمريكي، أو العكس.

في عالم البرمجة، يحدث هذا عادةً عند مزج المكتبات معًا التي تقدم نفس الوظيفة (في سياقنا، يتم شحن وظيفتنا المشتركة). سنحتاج إلى إيجاد طريقة لتمكين ذلك.

إذا اتبعنا هذا القياس، فسنحتاج إلى الذهاب إلى متجر إلكترونيات وشراء محول يمكّننا من شحن الأجهزة الأمريكية باستخدام الشواحن البريطانية. من وجهة نظر البرمجة، سنكون نحن مصنّع هذا المحول.

سنقوم بإنشاء مُهايئ يُطابق النمط المطلوب تمامًا لإنشاء المُهايئ الآخر. سنُنفذه كفئة كما يلي. ليس من الضروري أن يكون فئة، بل يُمكن أن يكون دالة تُوضح وظيفة نمط المُهايئ بشكل عام. نستخدم فئة لأنها تُناسب معظم الاستخدامات. Android.

class AmericanToBritishChargerAdapter(theAmericanCharger:AmericanCharger){
    fun returnNewCharger(): BritishCharger{
            //convert the American charger to a BritishCharger
            //we would change the American charging functionality
            //to British charging functionality to make sure the
            //adapter doesn't destroy the device. The adapter could
            //, for example, control the power output by dividing by 2
            //our adapter could encompass this functionality in here

           var chargingPower:Int = theAmericanCharger.chargingPower / 2
           var newBritishCharger = new BritishCharger()
           newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2

        return newBritishCharger
    }
}

في عالم البرمجة، يُشابه اختلاف المقابس اختلاف طرق الشحن. فوجود طرق شحن مختلفة في أجهزة الشحن يجعل استخدامها مستحيلاً.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()

محاولة استدعاء دالة charge() على جهازي البريطاني باستخدام americanChargerIFound لن تنجح، لأن الجهاز الأمريكي لا يقبل إلا AmericanCharger. لذا، من المستحيل القيام بذلك.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
myBritishDevice.charge(americanChargerIFound)

في هذا السيناريو، يُمكننا الاستفادة من المحوّل الذي أنشأناه، AmericanToBritishChargerAdapter. يُمكننا استخدام الدالة returnNewCharger() لإنشاء شاحن بريطاني جديد، والذي يُمكننا استخدامه للشحن. كل ما علينا فعله هو إنشاء نسخة من المحوّل وتزويده بالشاحن الأمريكي الموجود لدينا، وسيقوم بإنشاء شاحن بريطاني جاهز للاستخدام.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
//We create the adapter and feed it the americanCharger
var myAdapter =  AmericanToBritishChargerAdapter(americanChargerIFound)
//calling returnNewCharger from myAdapter would return a BritishCharger
var britishChargerFromAdapter = myAdapter.returnNewCharger()
//and once we have the britishCharger we can now use it
myBritishDevice.charge(britishChargerFromAdapter)

RecyclerView.LayoutManager

عند التعامل مع مجموعة عرض (ViewGroup)، يتم وضع عناصر العرض (Views) بداخلها. ويتولى مدير التخطيط (LayoutManager) مهمة وصف كيفية ترتيب عناصر العرض داخلها.

للمقارنة، عند العمل مع عنصر LinearLayout ViewGroup، فإن حالة الاستخدام التي نريدها هي القدرة على وضع العناصر إما عموديًا أو أفقيًا. يمكن تحقيق ذلك بسهولة عن طريق إضافة سمة توجيه، والتي تحدد لنا كيفية وضع LinearLayout على الشاشة. يمكننا القيام بذلك باستخدام android:orientation=VERTICAL|HORIZONTAL السمة.

لدينا أيضًا مجموعة عرض أخرى تُسمى GridLayout. تُستخدم هذه المجموعة عندما نرغب في وضع عناصر العرض في بنية شبكية مستطيلة. قد يكون ذلك لأسباب مثل تسهيل استيعاب البيانات التي نعرضها لمستخدم التطبيق. تتيح GridLayout، بحسب تصميمها، إمكانية ضبط الإعدادات لتحقيق هذا الهدف من خلال تحديد أبعاد الشبكة؛ على سبيل المثال، يمكننا إنشاء شبكة 4×4 أو شبكة 3×2.

RecyclerView.ViewHolder

حامل العرض هو مادةtracفئة t التي نرثها أيضًا من RecyclerView. يوفر لنا ViewHolder طرقًا مشتركة لمساعدتنا في الإشارة إلى عرض وضعناه على RecyclerView، حتى بعد أن تُغير آلية إعادة التدوير في RecyclerView مراجع مختلفة لا نعرفها.

قوائم كبيرة

تُستخدم RecyclerViews عندما نريد عرض مجموعة كبيرة جدًا من طرق العرض للمستخدم، كل ذلك دون استنفاد المساحة المتاحة. رامات على أجهزتنا لكل مثيل للعرض الذي تم إنشاؤه.

إذا أخذنا مثال قائمة جهات الاتصال، فسيكون لدينا فكرة عامة عن شكل كل جهة اتصال في القائمة. ما سنفعله بعد ذلك هو إنشاء تصميم نموذجي، وهو في الواقع عرض، يحتوي على خانات تُملأ ببيانات مختلفة من قائمة جهات الاتصال. فيما يلي رمز برمجي زائف يوضح الغرض من ذلك:

//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>

عندها سيكون لدينا قائمة جهات اتصال من هذا النوع:

<ContactList>
</ContactList>

لو كنا نُضمّن المحتويات مباشرةً في الكود، لما كان لدينا طريقة برمجية لإضافة محتوى جديد إلى القائمة دون إعادة كتابة التطبيق. لحسن الحظ، إضافة عنصر عرض إلى مجموعة عرض مدعومة بواسطة... addView(view:View) مع ذلك، ليست هذه هي الطريقة التي يتم بها إضافة عناصر العرض الفرعية إلى RecyclerView.

في حالتنا، سيكون لدينا قائمة طويلة من جهات الاتصال. لكل جهة اتصال في القائمة، سنحتاج إلى إنشاء OneContactView وتعبئة البيانات داخله لتتوافق مع الحقول الموجودة في فئة Contact. بعد ذلك، بمجرد إنشاء العرض، سنضيفه إلى RecyclerView لعرض القائمة.

data  class Contact(var name:String, var address:String, var pic:String, var phoneNumber:Int)

var contact1 = Contact("Guru","Guru97", "SomePic1.jpg", 991)
var contact2 = Contact("Guru","Guru98", "SomePic2.jpg", 992)
var contact3 = Contact("Guru","Guru99", "SomePic3.jpg", 993)

var myContacts:ArrayList<Contact> = arrayListOf<Contact>(contact1,contact2,contact3)

لدينا مجموعة من جهات الاتصال. يحتوي OneContactView على خانات لعرض محتويات فئة Contact. في RecyclerView، يجب إضافة عناصر Views إليه لكي يُسهّل علينا إعادة تدوير البيانات.

لا يسمح لنا RecyclerView فعليًا بإضافة عرض، ولكنه يُمكّننا من إضافة ViewHolder. لذا، في هذا السيناريو، لدينا جزآن نريد ربطهما لكنهما غير متطابقين. وهنا يأتي دور المُهايئ (Adapter). يوفر لنا RecyclerView مُهايئًا، تمامًا مثل... AmericanToBritishChargerAdapter() من السابق، مكننا ذلك من تحويل الشاحن الأمريكي الخاص بنا، والذي كان غير قابل للاستخدام مع جهازنا البريطاني، إلى شيء قابل للاستخدام، يشبه محول الطاقة في الحياة الواقعية.

في هذا السيناريو، سيأخذ المحول مصفوفة جهات الاتصال الخاصة بنا وعرضنا، ومن ثم يقوم بإنشاء ViewHolders التي يرغب RecyclerView في قبولها.

توفر RecyclerView واجهةً يمكننا توسيعها لإنشاء محوّلنا الخاص من خلال فئة RecyclerView.Adapter. يحتوي هذا المحوّل على طريقة لإنشاء فئة ViewHolder التي ترغب RecyclerView في التعامل معها. إذن، لدينا نفس الوضع السابق، ولكن مع إضافة عنصر واحد، وهو المحوّل.

لدينا مصفوفة من جهات الاتصال، وواجهة لعرض جهة اتصال واحدة (OneContactView)، وRecyclerView، وهي قائمة من الواجهات التي توفر خدمات إعادة التدوير ولكنها تقبل فقط ViewHolders. في هذه الحالة، لدينا الآن فئة RecyclerView.Adapter، والتي تحتوي على دالة لإنشاء ViewHolders بداخلها.

fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder

RecyclerView.ViewHolder هو مطلقtracفئة تأخذ العرض الخاص بنا كوسيط وتحوله إلى ViewHolder. تستخدم نمط التغليف المستخدم لتوسيع قدرات الفئات.

الأساس رقم 2 - نمط الغلاف

سنستخدم مثالاً بسيطاً لتوضيح كيف يمكننا جعل الحيوانات تتكلم.

sealed trait Animal{
    fun sound():String
}

data class Cat(name:String):Animal{
    fun sound(){
        "Meow"
        }
}
data class Dog(name:String):Animal{
    fun sound(){
        "Woof"
        }
}

var cat1 = Cat("Tubby")
var dog1 = Dog("Scooby")
cat1.sound() //meow
dog1.sound() //woof

في المثال السابق، لدينا حيوانان. إذا أردنا، مصادفةً، إضافة طريقة لجعلهما يتكلمان، ولكن لم يكن مطور المكتبة متعاونًا، فلا يزال بإمكاننا إيجاد حل. ما نحتاجه هو غلاف لفئة الحيوان. سنفعل ذلك باستقبال الحيوان كدالة إنشاء لفئتنا.

class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

الآن، يمكننا تمرير كائن حيوان إلى SpeechPoweredAnimalByWrapper. سيؤدي استدعاء دالة sound() عليه إلى استدعاء دالة sound() الخاصة بالحيوان المُمرَّر. لدينا أيضًا دالة speak() إضافية، تُعتبر وظيفة جديدة نضيفها إلى الكائنات المُمرَّرة. يمكننا استخدامها كما يلي:

var cat1 =  Cat("Garfield")
cat1.sound()//"meow"
cat1.speak()// doesn't work as it isn't implemented
var talkingCat = new SpeechPoweredAnimalByWrapper(cat1)
talkingCat.sound() //"meow" the sound method calls the one defined for cat1
talkingCat.speak() //"Hello, my name is Garfield"

باستخدام هذا النمط، يمكننا أخذ الفئات وإضافة وظائف جديدة إليها. كل ما نحتاجه هو تمرير نسخة من الفئة والأساليب الجديدة المُعرَّفة بواسطة غلافنا.ping فئة.

في المثال أعلاه، استخدمنا فئة ملموسة. من الممكن أيضًا تطبيق نفس الشيء في فئة مطلقة.tracالفئة t. سنحتاج إلى تغيير فئة SpeechPoweredAnimalByWrapper إلى abstract، وقد انتهينا. سنغير اسم الفئة إلى اسم أقصر لجعله أكثر وضوحًا.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

الأمر كما كان من قبل، لكنه سيحمل معنى مختلفًا. في فئة عادية، يمكننا إنشاء نسخة من فئة بنفس الطريقة التي أنشأنا بها cat1 و dog1.tracمع ذلك، فإن فئات t ليست مخصصة لإنشاء مثيلات منها، بل لتوسيع فئات أخرى. فكيف نستخدم إذًا الدالة الجديدة SpeechPowered(var myAnimal:Animal) abs؟tracهل يمكننا استخدام فئة t؟ يمكننا استخدامها عن طريق إنشاء فئات جديدة توسعها، وبالتالي تكتسب وظائفها.

في مثالنا، سنقوم بإنشاء فئة SpeechPoweredAnimal التي تمتد من الفئة.

class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 =  Cat("Tubby")
var speakingKitty = SpeechPoweredAnimal(cat1)
speakingKitty.speak() //"Hello, my name is Tubby"

هذا هو نفس النمط المستخدم في ViewHolder. الفئة RecyclerView.ViewHolder هي نسخة مطلقةtracفئة t تُضيف وظائف إلى العرض، تمامًا كما أضفنا دالة التحدث إلى الحيوانات. هذه الوظائف الإضافية هي ما يجعلها تعمل عند التعامل مع RecyclerView.

هذه هي الطريقة التي سنقوم بها بإنشاء OneContactViewHolder من OneContactView:

//The View argument we pass is converted to a ViewHolder which uses the View to give it more abilities and in turn work with the RecyclerView
class OneContactViewHolder(ourContactView: View) : RecyclerView.ViewHolder(ourContactView)

يحتوي RecyclerView على محول يسمح لنا بربط مصفوفة جهات الاتصال الخاصة بنا بـ ContactsView باستخدام RecyclerView.

إضافة طريقة عرض

لا يتم إعادة رسم ViewGroup تلقائيًا، بل يتبع جدولًا زمنيًا محددًا. قد يحدث على جهازك أن يتم إعادة الرسم كل 10 مللي ثانية أو 100 مللي ثانية؛ أو، إذا اخترنا رقمًا غير منطقي، لنقل دقيقة واحدة، فعند إضافة View إلى ViewGroup، سترى التغييرات بعد دقيقة واحدة عندما يتم "تحديث" ViewGroup.

RecyclerView.Recycler

العمل التأسيسي رقم 3 - التخزين المؤقت

يُعدّ المتصفح أحد أفضل الأمثلة على المواقع التي نقوم فيها بتحديث الصفحة بشكل منتظم. لنفترض، على سبيل المثال، أن الموقع الذي نزوره ثابت ولا يُرسل محتوى ديناميكيًا؛ سنحتاج حينها إلى تحديث الصفحة باستمرار لرؤية التغييرات.

لنفترض في هذا المثال أن الموقع المعني هو تويتر. سنرى سلسلة من التغريدات الثابتة، والطريقة الوحيدة لرؤية التغريدات الجديدة هي النقر على زر التحديث لإعادة جلب المحتوى.

من الواضح أن إعادة رسم الشاشة بأكملها أمر مكلف. تخيل لو كانت لدينا سعة إنترنت محدودة مع مزود خدمة الهاتف، وكانت قائمة تغريداتنا تحتوي على الكثير من الصور والفيديوهات؛ سيكون من المكلف إعادة تحميل جميع محتويات الصفحة في كل مرة يتم فيها تحديث الصفحة.

نحتاج إلى طريقة لتخزين التغريدات المحملة مسبقًا، والتأكد من قدرة الطلب التالي على تحديد التغريدات الموجودة لديه. وبالتالي، لا يُعاد تحميل جميع التغريدات، بل تُحمّل التغريدات الجديدة فقط، مع التحقق من وجود أي تغريدة محفوظة محليًا لحذفها. ما نصفه يُسمى التخزين المؤقت.

تُسمى المعلومات التي نرسلها إلى الموقع الإلكتروني حول المحتوى الموجود لدينا بالبيانات الوصفية. لذا، في الواقع، نحن لا نقول ببساطة "نريد تحميل موقعك"، بل نقول "نريد تحميل موقعك، وهذه بعض المحتويات التي حفظناها مسبقًا من آخر مرة قمنا فيها بالتحميل؛ يُرجى استخدامها لإرسال ما ليس موجودًا فقط، حتى لا نستهلك الكثير من النطاق الترددي".

مكالمات التخطيط – يجب أن تكون قائمة التغريدات مجنونة

من أمثلة استدعاءات التخطيط دالة scrollToPosition. وهي دالة شائعة في تطبيقات الدردشة. فعندما يردّ أحد المستخدمين في محادثة على رسالة سابقة، تُضمّن بعض تطبيقات الدردشة الردّ ورابطًا إلى الرسالة الأصلية، والذي عند النقر عليه يُعيد المستخدم إلى مكان الرسالة الأصلية.

في حالة استدعاء هذه الطريقة قبل إضافة LayoutManager إلى RecyclerView وقبل وجود RecyclerView.Adapter، يتم تجاهل scrollToPosition(n:Int) ببساطة.

التواصل بين مكونات RecyclerView

الأعمال التأسيسية #4 – معاودة الاتصال

تتضمن وظيفة RecyclerView العديد من العناصر المتداخلة. فهي تتعامل مع LayoutManager، الذي يحدد كيفية تنظيم العناصر، إما بشكل خطي أو في شبكة. كما تتعامل مع محول يقوم بتحويل العناصر (contactList) إلى عناصر عرض (OneContactView)، ثم إلى ViewHolders (OneContactViewHolder) التي يمكن لـ RecyclerView التعامل معها باستخدام الطرق التي يوفرها.

المادة الخام لـ RecyclerView هي طرق العرض الخاصة بنا، على سبيل المثال OneContactView، ومصدر البيانات.

contactList:Array<Contact>

استخدمنا سيناريو بسيطًا كنقطة انطلاق لفهم ما يسعى إليه RecyclerView. الحالة الأساسية، وهي عرض مصفوفة ثابتة تضم 1000 جهة اتصال للمستخدم، سهلة الفهم. لكن آلية عمل RecyclerView تبدأ بالعمل فعليًا عندما تصبح القائمة ديناميكية. مع القائمة الديناميكية، علينا التفكير في كيفية تغير العرض على الشاشة عند إضافة عنصر إلى القائمة أو حذفه منها.

RecyclerView.LayoutManager

إلى جانب تحديد كيفية عرض واجهات المستخدم، سواء بشكل خطي أو في شبكة، يقوم مدير التخطيط بالكثير من العمل في الخلفية.ping يعرف القائمون على إعادة التدوير متى يقومون بإعادة التدوير.

وهو مسؤول عن الحفاظ علىping tracيقوم مدير التخطيط (LayoutManager) بفحص عدد العناصر المرئية حاليًا على الشاشة، وإرسال هذه المعلومات إلى آلية إعادة التدوير. وعندما يقوم المستخدم بالتمرير لأسفل، يكون مدير التخطيط مسؤولاً عن إبلاغ نظام إعادة التدوير بالعناصر التي تفقد التركيز في الأعلى، حتى يمكن إعادة استخدامها، بدلاً من بقائها هناك واستهلاك الذاكرة أو حذفها لإنشاء عناصر جديدة.

هذا يعني أن مدير التخطيط يحتاج إلى الاحتفاظ tracيُحدد النظام موقع المستخدم أثناء تصفحه للقائمة. ويتم ذلك من خلال قائمة مواقع مُفهرسة، أي أن العنصر الأول يبدأ من 0 ويتزايد ليتناسب مع عدد العناصر في القائمة.

إذا كان بإمكاننا عرض 10 عناصر من قائمتنا التي تضم، على سبيل المثال، 100 عنصر، فإن مدير التخطيط يكون على دراية في البداية بأنه يركز على العرض 0 وصولاً إلى العرض 9. ومع استمرار التمرير، يستطيع مدير التخطيط حساب العروض التي تفقد التركيز.

يستطيع مدير التخطيط تحرير هذه العروض لآلية إعادة التدوير بحيث يمكن إعادة استخدامها (يمكن ربط بيانات جديدة بها؛ على سبيل المثال، يمكن إزالة بيانات الاتصال الخاصة بالعرض ويمكن استبدال العناصر النائبة ببيانات اتصال جديدة من الشريحة التالية).

سيكون هذا مثاليًا لو كانت القائمة ثابتة، لكن من أكثر استخدامات RecyclerView شيوعًا هي القوائم الديناميكية التي قد تأتي بياناتها من مصدر عبر الإنترنت أو حتى من مستشعر. لا يقتصر الأمر على إضافة البيانات، بل قد تُحذف أو تُحدّث بيانات أخرى من القائمة.

قد تجعل الحالة الديناميكية لبياناتنا من الصعب للغاية فهم وظيفة مدير التخطيط. لهذا السبب، يحتفظ مدير التخطيط بقائمة خاصة به تتضمن العناصر ومواقعها، منفصلة عن القائمة التي يستخدمها مكون إعادة التدوير. وهذا يضمن قيامه بمهمة التخطيط على النحو الأمثل.

في الوقت نفسه، يحرص مدير التخطيط (LayoutManager) الخاص بـ RecyclerView على عدم تحريف البيانات الموجودة لديه. ولضمان عمله بشكل صحيح، يقوم مدير التخطيط بمزامنة البيانات مع RecyclerView.Adapter على فترات زمنية محددة (60 مللي ثانية)، حيث يتبادل معلومات حول عناصر القائمة، أي العناصر المضافة أو المحدثة أو المحذوفة أو المنقولة من موضع إلى آخر. وعند استلام هذه المعلومات، يقوم مدير التخطيط بإعادة تنظيم المحتوى على الشاشة ليتوافق مع التغييرات عند الضرورة.

تدور العديد من العمليات الأساسية التي تتعامل مع RecyclerView حول الاتصال بين RecyclerView.LayoutManager و RecyclerView.Adapter الذي يخزن قوائمنا من البيانات الثابتة أحيانًا أو الديناميكية أحيانًا أخرى.

بالإضافة إلى ذلك، يوفر لنا RecyclerView طرقًا يمكننا استخدامها للاستماع إلى الأحداث، مثل onBindViewHolder عندما يقوم RecyclerView.Adapter بربط المحتوى من قائمتنا (مثل جهة اتصال) بـ ViewHolder بحيث يتم استخدامه لعرض المعلومات على الشاشة.

أما الخاصية الأخرى فهي onCreateViewHolder، والتي تخبرنا متى يقوم RecyclerView.Adapter بأخذ عرض عادي مثل OneContactView وتحويله إلى عنصر ViewHolder يمكن لـ RecyclerView العمل معه.

إلى جانب الآليات الأساسية التي تُمكّن من إعادة التدوير، يوفر RecyclerView طرقًا لتخصيص السلوك دون التأثير على إعادة التدوير. إعادة استخدام طرق العرض تجعل من الصعب القيام بالأمور الشائعة التي اعتدنا القيام بها مع طرق العرض الثابتة، مثل الاستجابة لأحداث النقر.

كما نعلم، RecyclerView.LayoutManager الذي يعرض طرق العرض للمستخدم قد يحتوي للحظة على قائمة مختلفة من العناصر عن RecyclerView.Adapter تحتوي هذه القائمة على بيانات مخزنة في قاعدة بيانات أو يتم استيرادها من مصدر. قد يؤدي وضع أحداث النقر مباشرةً على طرق العرض إلى سلوك غير متوقع، مثل حذف جهة الاتصال الخاطئة.

Gradle

إذا أردنا استخدام RecyclerView، فعلينا إضافته كاعتمادية في ملف build.gradle. في المثال التالي، استخدمنا الإصدار "androidx.recyclerview:recyclerview:1.1.0"، وهو أحدث إصدار وفقًا لهذه المقالة.

بعد إضافة التبعية إلى ملفنا Gradle الملف، سوف تتم مطالبتنا به Android Studio لمزامنة التغييرات. هكذا تتم عملية Gradle سيبدو الملف بعد إضافة RecyclerView في مشروع فارغ باستخدام الإعدادات الافتراضية فقط.

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.guru99.learnrecycler"
        minSdkVersion 17
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
             'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    implementation "androidx.recyclerview:recyclerview:1.1.0"
}

لدينا ملف تخطيط واحد فقط في الوقت الحالي. سنبدأ بمثال بسيط نستخدم فيه RecyclerView لعرض قائمة بأسماء الفواكه على الشاشة.

قائمة العناصر

سننتقل إلى ملف MainActivity الخاص بنا وننشئ مصفوفة بأسماء الفاكهة بداخلها، مباشرة قبل طريقة onCreate() التي تم إنشاؤها أثناء الإعداد.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

هدفنا التالي هو عرض هذه القائمة على الشاشة باستخدام RecyclerView. وللقيام بذلك، سننتقل إلى مجلد التخطيط الذي يحتوي على تخطيطاتنا، وننشئ عرضًا مسؤولًا عن عرض فاكهة واحدة.

التخطيط الذي سيتم استخدامه لكل عنصر في قائمتنا

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/fruitName"
    />
</TextView>

في عنصر TextView أعلاه، أضفنا حقل id يُستخدم لتعريف عنصر العرض. هذا الحقل غير مُنشأ تلقائيًا. وقد أعطينا عنصر TextView الخاص بنا المعرّف fruitName ليتوافق مع البيانات التي سيتم ربطها به.

إضافة RecyclerView إلى التخطيط الرئيسي

في نفس النشاط، يوجد ملف تخطيط main_layout.xml تم إنشاؤه تلقائيًا. إذا اخترنا مشروعًا فارغًا، فسيتم إنشاء ملف تخطيط آخر. XML يحتوي على ConstraintLayout، وبداخله TextView يحتوي على نص "Hello". سنحذف جميع المحتويات، وسيحتوي التخطيط فقط على RecyclerView كما يلي:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/fruitRecyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

لقد أضفنا أيضًا سمة id لـ RecyclerView، والتي سنستخدمها للإشارة إليها في التعليمات البرمجية الخاصة بنا.

android:id="@+id/fruitRecyclerView"

سنعود بعد ذلك إلى ملف MainActivity. باستخدام المعرّفات التي أنشأناها، سنتمكن من الإشارة إلى طرق العرض التي أنشأناها للتو. سنبدأ بالإشارة إلى RecyclerView باستخدام طريقة findViewById() التي يوفرها Androidسنقوم بذلك في دالة onCreate() الخاصة بنا. ستكون دالة onCreate() الخاصة بنا على النحو التالي.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
}

إنشاء ViewHolder

بعد ذلك، سنقوم بإنشاء RecyclerView.ViewHolder، وهو المسؤول عن تحويل العرض (View) إلى ViewHolder، الذي يستخدمه RecyclerView لعرض العناصر. سنقوم بذلك مباشرةً بعد استدعاء دالة onCreate().

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

}

إنشاء RecyclerViewAdapter

بعد ذلك، سنقوم بإنشاء فئة FruitArrayAdapter التي ترث من فئة RecyclerView.Adapter. ستكون فئة FruitArrayAdapter التي سننشئها مسؤولة عن ما يلي: ستأخذ أسماء الفاكهة من مصفوفة الفاكهة، وتنشئ ViewHolder باستخدام ملف العرض one_fruit_view.xml، ثم تربط الفاكهة بـ ViewHolder وتربط محتوياتها ديناميكيًا بالعرض الذي أنشأناه.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>()
}

Android Studio سيضيف ذلك خطوطًا حمراء متعرجة على FruitArrayAdapter الخاص بنا، مما يخبرنا أننا بحاجة إلى تنفيذ طرق يمكن لـ RecyclerView استخدامها لربط المصفوفة الخاصة بنا بـ ViewHolder.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

     }

     override fun getItemCount(): Int {

     }

     override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

     }
 }

سنبدأ بأسهل جزء من الكود المُولّد، وهو دالة getItemCount(). نعرف كيف نحصل على عدد العناصر في المصفوفة باستدعاء خاصية حجم المصفوفة.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

        }

        override fun getItemCount(): Int {
         return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

ثم سنقوم بتنفيذ الدالة onCreateViewHolder. هنا يطلب منا RecyclerView مساعدته في إنشاء FruitViewHolder. إذا تذكرنا، كان شكل فئة FruitViewHolder لدينا كالتالي:

class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

يتطلب ذلك ملف عرض الفاكهة (fruitView) الذي أنشأناه كملف XML باسم one_fruit_view.xml. يمكننا إنشاء مرجع لهذا الملف XML وتحويله إلى عرض (View) كما يلي.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder

        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

الجزء المتبقي هو تجاوز onBindViewHolder.

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

يطلب RecyclerView.Adapter قيمة عددية للموقع، والتي نستخدمها لجلب عنصر من قائمتنا. كما يوفر لنا حاويةً لربط العنصر الذي نحصل عليه من مصفوفة الفاكهة بالعرض الموجود داخلها. يمكن الوصول إلى العرض الموجود داخل حاوية العرض من خلال الحقل ViewHolder.itemView. بمجرد الحصول على العرض، يمكننا استخدام المعرّف fruitName الذي أنشأناه سابقًا لتعيين المحتوى.

override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

          var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

          var aFruitName = fruitArray.get(position)

          ourFruitTextView.setText(aFruitName)
      }

وبذلك، يكون برنامج FruitArrayAdapter الخاص بنا قد اكتمل ويبدو كما يلي.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }

وأخيرًا، نحن جاهزون لربط الأجزاء المتبقية من RecyclerView، والتي تتمثل في إنشاء LayoutManager الذي سيخبر RecyclerView بكيفية عرض محتويات القائمة، سواء كان ذلك بطريقة خطية باستخدام LinearLayoutManager أو في شبكة باستخدام GridLayoutManager أو StaggeredGridLayoutManager.

إنشاء مدير التخطيط

سنعود إلى داخل دالة onCreate الخاصة بنا ونضيف LayoutManager.

override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

       var fruitLinearLayout = LinearLayoutManager(this)

       myFruitRecyclerView.layoutManager =fruitLinearLayout

   }

قم بتوصيل المحول الخاص بنا بالعناصر وقم بتعيينه على RecyclerView

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

أنشأنا أيضًا نسخة من كائن fruitListAdapter وقمنا بتزويده بمصفوفة أسماء الفواكه. وبذلك نكون قد انتهينا. يبدو ملف MainActivity.kt الكامل كما يلي.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }
}

تحميل المشروع

الأسئلة الشائعة

يُعد RecyclerView خليفةً أكثر مرونة وكفاءةً لـ ListView. فهو يفرض نمط ViewHolder، ويدعم التخطيطات الخطية والشبكية والمتداخلة من خلال مديري التخطيط، ويضيف رسومًا متحركة مدمجة للعناصر، بينما لا يوفر ListView أيًا من هذه الميزات افتراضيًا.

يعرض GridView العناصر في شبكة ثابتة فقط. بينما يستطيع RecyclerView إعادة إنتاج شبكة باستخدام GridLayoutManager، ولكنه يدعم أيضًا التخطيطات الخطية والمتداخلة، وإعادة تدوير العرض، والرسوم المتحركة، مما يجعله أكثر تنوعًا من GridView.

هذا المثال الخاص بـ RecyclerView مكتوب بلغة Kotlin، وهي اللغة الموصى بها رسميًا من قبل Google لـ Android التطوير. تعمل واجهات برمجة التطبيقات نفسها الخاصة بـ RecyclerView والمحول ومدير التخطيط أيضًا في Java.

قم بإضافة مستمع نقرات داخل ViewHolder أو onBindViewHolder، ثم اعرضه من خلال دالة رد نداء للواجهة في النشاط أو الجزء. استخدام موضع ViewHolder يمنع ربط النقرات بالعنصر المُعاد تدويره الخاطئ.

DiffUtil هي فئة مساعدة تحسب الفرق بين قائمتين وترسل الحد الأدنى من التحديثات إلى المحول. تجعل هذه الفئة تغييرات القوائم الديناميكية فعالة وتُحرك عمليات الإضافة والحذف والنقل تلقائيًا.

يستخدم Jetpack Compose كلاً من LazyColumn وLazyRow بدلاً من RecyclerView لإنشاء شاشات جديدة. مع ذلك، لا يزال RecyclerView شائع الاستخدام في المشاريع القائمة على العرض، ويمكن تضمينه داخل Compose من خلال واجهات برمجة التطبيقات (APIs) الخاصة بالتوافق.

نعم. يمكن لمساعدي الذكاء الاصطناعي، مثل GitHub Copilot والأدوات القائمة على GPT، إنشاء نموذج أولي لـ RecyclerView adapter وViewHolder وonBindViewHolder من خلال طلب بسيط. مع ذلك، ينبغي على المطورين مراجعة الكود المُنشأ للتأكد من صحته وأدائه.

تعلم الآلة Android تستخدم التطبيقات RecyclerView لعرض مخرجات ديناميكية مثل التوصيات، وترتيب نتائج البحث، وتصنيفات الصور. يقوم المحول بربط نتائج النموذج بعناصر القائمة، مما يسمح بتمرير مجموعات البيانات الكبيرة والمتغيرة باستمرار بسلاسة.

تلخيص هذه التدوينة بـ: