‫Android Kotlin Fundamentals 07.1: أساسيات RecyclerView

هذا الدرس التطبيقي حول الترميز هو جزء من دورة "أساسيات Android Kotlin". يمكنك تحقيق أقصى استفادة من هذه الدورة التدريبية إذا اتبعت ترتيب الخطوات في دروس البرمجة. يتم إدراج جميع الدروس التطبيقية حول الترميز الخاصة بالدورة التدريبية في الصفحة المقصودة للدروس التطبيقية حول الترميز في دورة Android Kotlin Fundamentals.

مقدمة

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

ما يجب معرفته

يجب أن تكون على دراية بما يلي:

  • إنشاء واجهة مستخدم أساسية باستخدام نشاط وقِطع وعناصر عرض
  • التنقّل بين الأجزاء واستخدام safeArgs لنقل البيانات بين الأجزاء
  • استخدام نماذج العرض ومصانع نماذج العرض وعمليات التحويل وLiveData ومراقبيه
  • إنشاء قاعدة بيانات Room وإنشاء كائن الوصول إلى البيانات (DAO) وتحديد الكيانات
  • استخدام إجراءات فرعية لمهام قاعدة البيانات والمهام الأخرى الطويلة الأمد

أهداف الدورة التعليمية

  • كيفية استخدام RecyclerView مع Adapter وViewHolder لعرض قائمة بالعناصر

الإجراءات التي ستنفذّها

  • غيِّر تطبيق TrackMySleepQuality من الدرس السابق لاستخدام RecyclerView لعرض بيانات جودة النوم.

في هذا الدرس التطبيقي حول الترميز، ستنشئ جزء RecyclerView من تطبيق يتتبّع جودة النوم. يستخدم التطبيق قاعدة بيانات Room لتخزين بيانات النوم بمرور الوقت.

يحتوي تطبيق تتبُّع النوم المبتدئ على شاشتَين، يتم تمثيلهما بلقطات، كما هو موضّح في الشكل أدناه.

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

يستخدم هذا التطبيق بنية مبسطة تتضمّن وحدة تحكّم في واجهة المستخدم (ViewModel) وLiveData. يستخدم التطبيق أيضًا قاعدة بيانات Room لجعل بيانات النوم دائمة.

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

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

لتوفير الدعم لجميع حالات الاستخدام هذه، يوفّر Android أداة RecyclerView.

تتمثّل أكبر مزايا RecyclerView في أنّها فعّالة جدًا مع القوائم الكبيرة:

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

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

نمط المحوّل

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

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

تنفيذ RecyclerView

لعرض بياناتك في RecyclerView، تحتاج إلى الأجزاء التالية:

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

في هذه المهمة، ستضيف RecyclerView إلى ملف التصميم وتُعدّ Adapter لعرض بيانات النوم في RecyclerView.

الخطوة 1: إضافة RecyclerView مع LayoutManager

في هذه الخطوة، عليك استبدال ScrollView بـ RecyclerView في الملف fragment_sleep_tracker.xml.

  1. نزِّل تطبيق RecyclerViewFundamentals-Starter من GitHub.
  2. أنشئ التطبيق وشغِّله. لاحظ كيف يتم عرض البيانات كنص بسيط.
  3. افتح fragment_sleep_tracker.xml ملف التصميم في علامة التبويب تصميم في "استوديو Android".
  4. في لوحة شجرة المكوّنات، احذف ScrollView. يؤدي هذا الإجراء أيضًا إلى حذف TextView داخل ScrollView.
  5. في لوحة لوحة الألوان، انتقِل إلى قائمة أنواع المكوّنات على يمين الصفحة للعثور على الحاويات، ثم اختَرها.
  6. اسحب RecyclerView من لوحة لوحة الألوان إلى لوحة شجرة المكوّنات. ضَع RecyclerView داخل ConstraintLayout.

  1. إذا ظهر مربّع حوار يسألك عمّا إذا كنت تريد إضافة تبعية، انقر على حسنًا للسماح لـ "استوديو Android" بإضافة تبعية recyclerview إلى ملف Gradle. قد يستغرق الأمر بضع ثوانٍ، وبعد ذلك تتم مزامنة تطبيقك.

  1. افتح ملف build.gradle الخاص بالوحدة، وانتقِل إلى النهاية، ودَوِّن الاعتمادية الجديدة التي تبدو مشابهة للرمز البرمجي أدناه:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. التبديل مرة أخرى إلى fragment_sleep_tracker.xml
  2. في علامة التبويب نص، ابحث عن الرمز RecyclerView الموضّح أدناه:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. امنح RecyclerView id من sleep_list.
android:id="@+id/sleep_list"
  1. ضَع RecyclerView ليملأ الجزء المتبقي من الشاشة داخل ConstraintLayout. لإجراء ذلك، قيِّد أعلى RecyclerView بالزر بدء، وأسفله بالزر محو، وكل جانب بالعنصر الأصل. اضبط عرض وارتفاع التصميم على 0 وحدة بكسل مستقلة في "أداة تعديل التصميم" أو في XML باستخدام الرمز التالي:
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toTopOf="@+id/clear_button"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/stop_button"
  1. أضِف أداة إدارة تنسيق إلى ملف RecyclerView XML. يحتاج كل RecyclerView إلى مدير تنسيق يحدّد كيفية ترتيب العناصر في القائمة. يوفر نظام التشغيل Android LinearLayoutManager، الذي يعرض العناصر تلقائيًا في قائمة عمودية من الصفوف ذات العرض الكامل.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. انتقِل إلى علامة التبويب التصميم ولاحظ أنّ القيود المضافة قد أدّت إلى توسيع RecyclerView لملء المساحة المتاحة.

الخطوة 2: إنشاء تخطيط عنصر القائمة وعنصر نائب لعرض النص

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

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

  1. أنشئ ملف تخطيط باسم text_item_view.xml. لا يهمّ العنصر الذي تستخدمه كعنصر أساسي، لأنّك ستستبدل رمز النموذج.
  2. في text_item_view.xml، احذف كل الرمز البرمجي المحدّد.
  3. أضِف TextView مع مساحة متروكة بحجم 16dp في البداية والنهاية، وحجم نص يبلغ 24sp. يجب أن يتطابق العرض مع العرض الرئيسي، وأن يلتف الارتفاع حول المحتوى. بما أنّ طريقة العرض هذه تظهر داخل RecyclerView، ليس عليك وضع طريقة العرض داخل ViewGroup.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:textSize="24sp"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"
    android:layout_width="match_parent"       
    android:layout_height="wrap_content" />
  1. فتح "Util.kt" انتقِل إلى النهاية وأضِف التعريف الموضّح أدناه، ما يؤدي إلى إنشاء الفئة TextItemViewHolder. ضَع الرمز في أسفل الملف، بعد آخر قوس إغلاق. يتم وضع الرمز في Util.kt لأنّ عنصر العرض هذا مؤقت، وسيتم استبداله لاحقًا.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. إذا طُلب منك ذلك، استورِد android.widget.TextView وandroidx.recyclerview.widget.RecyclerView.

الخطوة 3: إنشاء SleepNightAdapter

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

  1. في حزمة sleeptracker، أنشئ فئة Kotlin جديدة باسم SleepNightAdapter.
  2. اجعل SleepNightAdapter الصف يمتد RecyclerView.Adapter. يُطلق على الفئة اسم SleepNightAdapter لأنّها تحوّل العنصر SleepNight إلى عنصر يمكن أن يستخدمه RecyclerView. يحتاج المحوّل إلى معرفة عنصر حاوية العرض الذي سيتم استخدامه، لذا عليك إدخال TextItemViewHolder. استورِد المكوّنات اللازمة عند مطالبتك بذلك، وسيظهر لك خطأ لأنّ هناك طرقًا إلزامية يجب تنفيذها.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. في المستوى الأعلى من SleepNightAdapter، أنشئ متغيّر listOf SleepNight لتخزين البيانات.
var data =  listOf<SleepNight>()
  1. في SleepNightAdapter، استبدِل getItemCount() لعرض حجم قائمة ليالي النوم في data. يحتاج RecyclerView إلى معرفة عدد العناصر التي يتضمّنها المحوّل لعرضها، ويتم ذلك من خلال استدعاء getItemCount().
override fun getItemCount() = data.size
  1. في SleepNightAdapter، ألغِ وظيفة onBindViewHolder()، كما هو موضّح أدناه.

    يتم استدعاء الدالة onBindViewHolder() من خلال RecyclerView لعرض بيانات عنصر واحد من القائمة في الموضع المحدّد. لذلك، تأخذ طريقة onBindViewHolder() وسيطتَين: عنصر نائب للعرض وموضع البيانات المطلوب ربطها. في هذا التطبيق، العنصر الحاوي هو TextItemViewHolder، والموضع هو الموضع في القائمة.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. داخل onBindViewHolder()، أنشئ متغيّرًا لعنصر واحد في موضع معيّن في البيانات.
 val item = data[position]
  1. يحتوي ViewHolder الذي أنشأته على موقع يُسمّى textView. داخل onBindViewHolder()، اضبط text الخاص بـ textView على رقم جودة النوم. لا يعرض هذا الرمز سوى قائمة بالأرقام، ولكن يتيح لك هذا المثال البسيط معرفة كيف يحصل المحوّل على البيانات في حاوية العرض وعلى الشاشة.
holder.textView.text = item.sleepQuality.toString()
  1. في SleepNightAdapter، يمكنك إلغاء وتنفيذ onCreateViewHolder()، الذي يتم استدعاؤه عندما يحتاج RecyclerView إلى عنصر نائب لعرض عنصر.

    تأخذ هذه الدالة مَعلمتَين وتعرض ViewHolder. تكون المَعلمة parent، وهي مجموعة العرض التي تحتوي على أداة عرض العرض، دائمًا RecyclerView. يتم استخدام المَعلمة viewType عندما تكون هناك طرق عرض متعددة في RecyclerView نفسه. على سبيل المثال، إذا وضعت قائمة بعروض نصية وصورة وفيديو في RecyclerView نفسه، ستحتاج الدالة onCreateViewHolder() إلى معرفة نوع العرض الذي يجب استخدامه.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. في onCreateViewHolder()، أنشئ مثيلاً من LayoutInflater.

    تعرف أداة إنشاء التصميم على كيفية إنشاء طرق عرض من تصاميم XML. يحتوي context على معلومات حول كيفية زيادة عدد المشاهدات بشكل صحيح. في أداة ربط لعرض قائمة قابلة لإعادة الاستخدام، عليك دائمًا تمرير سياق مجموعة العرض parent، وهو RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. في onCreateViewHolder()، أنشئ view من خلال الطلب من layoutinflater تضخيمه.

    مرِّر التنسيق بتنسيق XML للعرض، ومجموعة العرض parent للعرض. الوسيطة الثالثة، وهي وسيطة منطقية، هي attachToRoot. يجب أن تكون قيمة هذا الوسيط false، لأنّ RecyclerView يضيف هذا العنصر إلى بنية العرض الهرمية نيابةً عنك عند الحاجة.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. في onCreateViewHolder()، يمكنك إرجاع TextItemViewHolder تم شراؤه باستخدام view.
return TextItemViewHolder(view)
  1. يجب أن يتيح المحوّل لـ RecyclerView معرفة وقت تغيير data، لأنّ RecyclerView لا يعرف أي شيء عن البيانات. ولا يعرف سوى عن عناصر الحاوية التي يقدّمها المحوّل.

    لإخبار RecyclerView عندما تتغيّر البيانات التي يعرضها، أضِف أداة ضبط مخصّصة إلى المتغيّر data في أعلى فئة SleepNightAdapter. في الدالة الضابطة، امنح data قيمة جديدة، ثم استدعِ notifyDataSetChanged() لتفعيل إعادة رسم القائمة باستخدام البيانات الجديدة.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

الخطوة 4: إخبار RecyclerView بشأن المحوّل

يجب أن يعرف RecyclerView المحوّل الذي سيتم استخدامه للحصول على عناصر الحاوية.

  1. فتح "SleepTrackerFragment.kt"
  2. في onCreateview()، أنشئ محوّلاً. ضَع هذا الرمز بعد إنشاء نموذج ViewModel وقبل عبارة return.
val adapter = SleepNightAdapter()
  1. اربط adapter بـ RecyclerView.
binding.sleepList.adapter = adapter
  1. نظِّف مشروعك وأعِد إنشاءه لتعديل العنصر binding.

    إذا استمر ظهور أخطاء حول binding.sleepList أو binding.FragmentSleepTrackerBinding، ألغِ صحة ذاكرات التخزين المؤقت وأعِد التشغيل. (اختَر ملف > إبطال ذاكرات التخزين المؤقت / إعادة التشغيل).

    إذا شغّلت التطبيق الآن، لن تظهر أي أخطاء، ولكن لن ترى أي بيانات معروضة عند النقر على بدء، ثم إيقاف.

الخطوة 5: نقل البيانات إلى المحوّل

حتى الآن، لديك محوّل وطريقة للحصول على البيانات من المحوّل إلى RecyclerView. عليك الآن نقل البيانات إلى المحوّل من ViewModel.

  1. فتح "SleepTrackerViewModel"
  2. ابحث عن المتغيّر nights الذي يخزّن جميع ليالي النوم، وهو البيانات المطلوب عرضها. يتم ضبط المتغيّر nights من خلال استدعاء getAllNights() في قاعدة البيانات.
  3. أزِل private من nights، لأنّك ستنشئ مراقبًا يحتاج إلى الوصول إلى هذا المتغيّر. يجب أن يبدو تعريفك على النحو التالي:
val nights = database.getAllNights()
  1. في حزمة database، افتح SleepDatabaseDao.
  2. ابحث عن الدالة getAllNights(). لاحظ أنّ هذه الدالة تعرض قائمة بقيم SleepNight على شكل LiveData. وهذا يعني أنّ المتغيّر nights يتضمّن LiveData الذي يتم تعديله باستمرار من خلال Room، ويمكنك مراقبة nights لمعرفة وقت تغييره.
  3. فتح "SleepTrackerFragment"
  4. في onCreateView()، أسفل إنشاء adapter، أنشئ مراقبًا للمتغيّر nights.

    من خلال توفير viewLifecycleOwner للجزء باعتباره مالك دورة الحياة، يمكنك التأكّد من أنّ هذا المراقب لا يكون نشطًا إلا عندما يكون RecyclerView على الشاشة.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. داخل المراقب، عندما تحصل على قيمة غير فارغة (لـ nights)، عليك تعيين القيمة إلى data الخاص بالمحوّل. في ما يلي الرمز البرمجي المكتمل الخاص بالمراقب وإعداد البيانات:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. إنشاء التعليمات البرمجية وتشغيلها

    ستظهر لك أرقام جودة النوم في قائمة إذا كان المحوّل يعمل. تعرض لقطة الشاشة على اليمين القيمة -1 بعد النقر على بدء. تعرض لقطة الشاشة على اليسار رقم جودة النوم المعدَّل بعد النقر على إيقاف واختيار تقييم الجودة.

الخطوة 6: استكشاف كيفية إعادة استخدام عناصر الحاوية

تعمل RecyclerView RecyclerView على إعادة تدوير حاويات العرض، ما يعني أنّها تعيد استخدامها. عندما يتم التمرير سريعًا لعرض ما يظهر على الشاشة، تعيد RecyclerView استخدام العرض للعرض الذي سيتم التمرير سريعًا لعرضه على الشاشة.

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

على سبيل المثال، يمكنك ضبط لون النص على الأحمر في عناصر عرض تتضمّن تقييمات جودة أقل من أو تساوي 1 وتمثّل النوم السيئ.

  1. في الفئة SleepNightAdapter، أضِف الرمز التالي في نهاية onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. شغِّل التطبيق.
  2. أضِف بعض بيانات جودة النوم المنخفضة، وسيظهر الرقم باللون الأحمر.
  3. أضِف تقييمات عالية لجودة النوم إلى أن يظهر رقم أحمر مرتفع على الشاشة.

    بما أنّ RecyclerView يعيد استخدام عناصر العرض، سيعيد في النهاية استخدام أحد عناصر العرض الحمراء لتقديم تقييم عالي الجودة. يتم عرض التقييم العالي باللون الأحمر عن طريق الخطأ.

  1. لحلّ هذه المشكلة، أضِف عبارة else لضبط اللون على الأسود إذا لم تكن الجودة أقل من أو تساوي واحدًا.

    مع توضيح الشرطين، سيستخدم عنصر الحاوية لون النص الصحيح لكل عنصر.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. شغِّل التطبيق، ويجب أن تكون الأرقام دائمًا باللون الصحيح.

تهانينا! لديك الآن RecyclerView أساسي يعمل بكامل طاقته.

في هذه المهمة، عليك استبدال أداة عرض بسيطة بأخرى يمكنها عرض المزيد من البيانات المتعلقة بليلة النوم.

إنّ ViewHolder البسيط الذي أضفته إلى Util.kt يغلّف TextView في TextItemViewHolder.

class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

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

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

الخطوة 1: إنشاء تخطيط العنصر

في هذه الخطوة، ستنشئ ملف التنسيق لعنصر واحد. يتألف التصميم من ConstraintLayout مع ImageView لجودة النوم وTextView لمدة النوم وTextView للجودة كنص. بما أنّك سبق لك إنشاء تخطيطات، انسخ رمز XML المقدَّم والصِقه.

  1. أنشئ ملفًا جديدًا لمورد التنسيق وسمِّه list_item_sleep_night.
  2. استبدِل كل الرمز في الملف بالرمز أدناه. بعد ذلك، تعرَّف على التنسيق الذي أنشأته للتو.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
   android:layout_height="wrap_content">

   <ImageView
       android:id="@+id/quality_image"
       android:layout_width="@dimen/icon_size"
       android:layout_height="60dp"
       android:layout_marginStart="16dp"
       android:layout_marginTop="8dp"
       android:layout_marginBottom="8dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       tools:srcCompat="@drawable/ic_sleep_5" />

   <TextView
       android:id="@+id/sleep_length"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginStart="8dp"
       android:layout_marginTop="8dp"
       android:layout_marginEnd="16dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toEndOf="@+id/quality_image"
       app:layout_constraintTop_toTopOf="@+id/quality_image"
       tools:text="Wednesday" />

   <TextView
       android:id="@+id/quality_string"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginTop="8dp"
       app:layout_constraintEnd_toEndOf="@+id/sleep_length"
       app:layout_constraintHorizontal_bias="0.0"
       app:layout_constraintStart_toStartOf="@+id/sleep_length"
       app:layout_constraintTop_toBottomOf="@+id/sleep_length"
       tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
  1. انتقِل إلى علامة التبويب التصميم في "استوديو Android". في "طريقة عرض التصميم"، يبدو التخطيط كما في لقطة الشاشة على اليمين أدناه. في عرض المخطط، يبدو مثل لقطة الشاشة على اليسار.

الخطوة 2: إنشاء ViewHolder

  1. فتح "SleepNightAdapter.kt"
  2. أنشئ فئة داخل SleepNightAdapter باسم ViewHolder واجعلها توسّع RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. داخل ViewHolder، احصل على مراجع لطرق العرض. تحتاج إلى مرجع إلى طرق العرض التي سيعدّلها ViewHolder. في كل مرة تربط فيها ViewHolder، عليك الوصول إلى الصورة وكلتا طريقتَي عرض النص. (يمكنك تحويل هذا الرمز لاستخدام ربط البيانات لاحقًا).
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)

الخطوة 3: استخدام ViewHolder في SleepNightAdapter

  1. في تعريف SleepNightAdapter، استخدِم SleepNightAdapter.ViewHolder الذي أنشأته للتو بدلاً من TextItemViewHolder.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

تعديل onCreateViewHolder():

  1. غيِّر توقيع onCreateViewHolder() لعرض ViewHolder.
  2. غيِّر أداة إنشاء التنسيق لاستخدام مورد التنسيق الصحيح، list_item_sleep_night.
  3. إزالة البث إلى TextView
  4. بدلاً من عرض TextItemViewHolder، اعرض ViewHolder.

    في ما يلي دالة onCreateViewHolder() المُعدَّلة المكتملة:
    override fun onCreateViewHolder(
            parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = 
            LayoutInflater.from(parent.context)
        val view = layoutInflater
                .inflate(R.layout.list_item_sleep_night, 
                         parent, false)
        return ViewHolder(view)
    }

تعديل onBindViewHolder():

  1. غيِّر توقيع onBindViewHolder() لكي تكون المَعلمة holder من النوع ViewHolder بدلاً من TextItemViewHolder.
  2. داخل onBindViewHolder()، احذف كل الرموز باستثناء تعريف item.
  3. حدِّد val res يحتوي على مرجع إلى resources لطريقة العرض هذه.
val res = holder.itemView.context.resources
  1. اضبط نص عرض النص sleepLength على المدة. انسخ الرمز أدناه، والذي يستدعي دالة تنسيق مضمّنة في الرمز الأولي.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. يؤدي ذلك إلى حدوث خطأ، لأنّه يجب تحديد convertDurationToFormatted(). افتح Util.kt وأزِل التعليق من الرمز وعمليات الاستيراد المرتبطة به. (اختَر الرمز > إضافة تعليق باستخدام تعليقات الأسطر).
  2. في onBindViewHolder()، استخدِم convertNumericQualityToString() لضبط الجودة.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. قد تحتاج إلى استيراد هذه الدوال يدويًا.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. اضبط الرمز الصحيح للجودة. يتم توفير رمز ic_sleep_active الجديد لك في رمز البداية.
holder.qualityImage.setImageResource(when (item.sleepQuality) {
   0 -> R.drawable.ic_sleep_0
   1 -> R.drawable.ic_sleep_1
   2 -> R.drawable.ic_sleep_2
   3 -> R.drawable.ic_sleep_3
   4 -> R.drawable.ic_sleep_4
   5 -> R.drawable.ic_sleep_5
   else -> R.drawable.ic_sleep_active
})
  1. إليك دالة onBindViewHolder() المعدَّلة النهائية التي تضبط جميع بيانات ViewHolder:
   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = data[position]
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. شغِّل تطبيقك. من المفترض أن تبدو شاشتك مثل لقطة الشاشة أدناه، حيث يظهر رمز جودة النوم، بالإضافة إلى نص يوضّح مدة النوم وجودته.

اكتملت عملية RecyclerView الآن. تعرّفت على كيفية تنفيذ Adapter وViewHolder، وجمّعت بينهما لعرض قائمة تتضمّن RecyclerView Adapter.

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

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

الخطوة 1: إعادة تصميم onBindViewHolder()

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

  1. في SleepNightAdapter، في onBindViewHolder()، حدِّد كل شيء باستثناء العبارة التي تحدّد المتغيّر item.
  2. انقر بزر الماوس الأيمن، ثم اختَر إعادة تصميم > استخراج > دالة.
  3. سمِّ الدالة bind واقبل المَعلمات المقترَحة. انقر على حسنًا.

    يتم وضع الدالة bind() أسفل onBindViewHolder().
    private fun bind(holder: ViewHolder, item: SleepNight) {
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. ضَع المؤشر على الكلمة holder من المَعلمة holder الخاصة بـ bind(). اضغط على Alt+Enter (Option+Enter على جهاز Mac) لفتح قائمة "النية". اختَر تحويل المَعلمة إلى متلقّي لتحويلها إلى دالة إضافة تتضمّن التوقيع التالي:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. قُص الدالة bind() والصِقها في ViewHolder.
  2. اجعل bind() علنيًا.
  3. استورِد bind() إلى المحوّل إذا لزم الأمر.
  4. بما أنّها أصبحت الآن في ViewHolder، يمكنك إزالة جزء ViewHolder من التوقيع. إليك الرمز النهائي للدالة bind() في الفئة ViewHolder.
fun bind(item: SleepNight) {
   val res = itemView.context.resources
   sleepLength.text = convertDurationToFormatted(
           item.startTimeMilli, item.endTimeMilli, res)
   quality.text = convertNumericQualityToString(
           item.sleepQuality, res)
   qualityImage.setImageResource(when (item.sleepQuality) {
       0 -> R.drawable.ic_sleep_0
       1 -> R.drawable.ic_sleep_1
       2 -> R.drawable.ic_sleep_2
       3 -> R.drawable.ic_sleep_3
       4 -> R.drawable.ic_sleep_4
       5 -> R.drawable.ic_sleep_5
       else -> R.drawable.ic_sleep_active
   })
}

الخطوة 2: إعادة تصميم onCreateViewHolder

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

  1. في onCreateViewHolder()، اختَر كل الرمز في نص الدالة.
  2. انقر بزر الماوس الأيمن، ثم اختَر إعادة تصميم > استخراج > دالة.
  3. سمِّ الدالة from واقبل المَعلمات المقترَحة. انقر على حسنًا.
  4. ضَع المؤشر على اسم الدالة from. اضغط على Alt+Enter (Option+Enter على جهاز Mac) لفتح قائمة "النية".
  5. انقر على الانتقال إلى العنصر المرافق. يجب أن تكون الدالة from() في عنصر مصاحب حتى يمكن استدعاؤها في الفئة ViewHolder، وليس في مثيل ViewHolder.
  6. انقل العنصر companion إلى الفئة ViewHolder.
  7. اجعل from() علنيًا.
  8. في onCreateViewHolder()، غيِّر عبارة return لعرض نتيجة استدعاء from() في الفئة ViewHolder.

    يجب أن تبدو طريقتَا onCreateViewHolder() وfrom() المكتملتان مثل الرمز البرمجي أدناه، ويجب أن يتم إنشاء الرمز البرمجي وتشغيله بدون أخطاء.
    override fun onCreateViewHolder(parent: ViewGroup, viewType: 
Int): ViewHolder {
        return ViewHolder.from(parent)
    }
companion object {
   fun from(parent: ViewGroup): ViewHolder {
       val layoutInflater = LayoutInflater.from(parent.context)
       val view = layoutInflater
               .inflate(R.layout.list_item_sleep_night, parent, false)
       return ViewHolder(view)
   }
}
  1. غيِّر توقيع الفئة ViewHolder لكي يكون الدالة الإنشائية خاصة. بما أنّ from() أصبحت الآن طريقة تعرض مثيلاً جديدًا من ViewHolder، لم يعُد هناك أي سبب يدعو أي شخص إلى استدعاء الدالة الإنشائية لـ ViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. شغِّل التطبيق. من المفترض أن يتم إنشاء التطبيق وتشغيله كما كان من قبل، وهو النتيجة المطلوبة بعد إعادة البناء.

مشروع "استوديو Android": RecyclerViewFundamentals

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

لعرض بياناتك في RecyclerView، تحتاج إلى الأجزاء التالية:

  • RecyclerView
    لإنشاء مثيل من RecyclerView، حدِّد عنصر <RecyclerView> في ملف التصميم.
  • LayoutManager
    يستخدم RecyclerView LayoutManager لتنظيم تخطيط العناصر في RecyclerView، مثل ترتيبها في شبكة أو في قائمة خطية.

    في <RecyclerView> في ملف التخطيط، اضبط السمة app:layoutManager على أداة إدارة التخطيط (مثل LinearLayoutManager أو GridLayoutManager).

    يمكنك أيضًا ضبط LayoutManager لـ RecyclerView آليًا. (سيتم تناول هذا الأسلوب في درس تطبيقي حول الترميز لاحقًا).
  • التصميم لكل عنصر
    أنشئ تصميمًا لعنصر واحد من البيانات في ملف تصميم XML.
  • المحوّل
    أنشئ محوّلاً يُعدّ البيانات ويحدّد طريقة عرضها في ViewHolder. اربط المحوّل بجهاز RecyclerView.

    عند تشغيل RecyclerView، سيستخدم المحوّل البرمجي لمعرفة كيفية عرض البيانات على الشاشة.

    يتطلّب المحوّل البرمجي تنفيذ الطرق التالية:
    getItemCount() لعرض عدد العناصر.
    onCreateViewHolder() لعرض ViewHolder لعنصر في القائمة.
    onBindViewHolder() لتكييف البيانات مع طرق العرض الخاصة بعنصر في القائمة.

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

دورة Udacity التدريبية:

مستندات مطوّري تطبيقات Android:

يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:

  • حدِّد واجبًا منزليًا إذا لزم الأمر.
  • توضيح كيفية إرسال الواجبات المنزلية للطلاب
  • وضع درجات للواجبات المنزلية

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

إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.

الإجابة عن هذه الأسئلة

السؤال 1

كيف يعرض RecyclerView العناصر؟ يُرجى اختيار جميع الإجابات المناسبة.

▢ يعرض العناصر في قائمة أو شبكة.

▢ يتم التمرير عموديًا أو أفقيًا.

▢ يمكن التمرير بشكل قطري على الأجهزة الأكبر حجمًا، مثل الأجهزة اللوحية.

▢ يسمح بتصميمات مخصّصة عندما لا تكون القائمة أو الشبكة كافية لحالة الاستخدام.

السؤال 2

ما هي مزايا استخدام RecyclerView؟ يُرجى اختيار جميع الإجابات المناسبة.

▢ عرض القوائم الكبيرة بكفاءة

▢ تعديل البيانات تلقائيًا

▢ يقلّل من الحاجة إلى إعادة التحميل عند تعديل عنصر أو حذفه أو إضافته إلى القائمة.

‫▢ تعيد استخدام طريقة العرض التي يتم الانتقال فيها خارج الشاشة لعرض العنصر التالي الذي يتم الانتقال فيه على الشاشة.

السؤال 3

ما هي بعض أسباب استخدام المحوّلات؟ يُرجى اختيار جميع الإجابات المناسبة.

▢ يؤدي فصل الاهتمامات إلى تسهيل تغيير التعليمات البرمجية واختبارها.

‫▢ RecyclerView لا يركّز على البيانات التي يتم عرضها.

▢ لا يجب أن تهتم طبقات معالجة البيانات بطريقة عرض البيانات.

▢ سيتم تشغيل التطبيق بشكل أسرع.

السؤال 4

أيّ مما يلي ينطبق على ViewHolder؟ يُرجى اختيار جميع الإجابات المناسبة.

▢ يتم تحديد تخطيط ViewHolder في ملفات تخطيط XML.

‫▢ هناك ViewHolder واحد لكل وحدة بيانات في مجموعة البيانات.

▢ يمكنك تضمين أكثر من ViewHolder واحد في RecyclerView.

‫▢ يربط Adapter البيانات بـ ViewHolder.

ابدأ الدرس التالي: ‫7.2: DiffUtil وربط البيانات باستخدام RecyclerView