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

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

- افتح ملف
build.gradleالخاص بالوحدة، وانتقِل إلى النهاية، ودَوِّن الاعتمادية الجديدة التي تبدو مشابهة للرمز البرمجي أدناه:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
- التبديل مرة أخرى إلى
fragment_sleep_tracker.xml - في علامة التبويب نص، ابحث عن الرمز
RecyclerViewالموضّح أدناه:
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />- امنح
RecyclerViewidمنsleep_list.
android:id="@+id/sleep_list"- ضَع
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"- أضِف أداة إدارة تنسيق إلى ملف
RecyclerViewXML. يحتاج كلRecyclerViewإلى مدير تنسيق يحدّد كيفية ترتيب العناصر في القائمة. يوفر نظام التشغيل AndroidLinearLayoutManager، الذي يعرض العناصر تلقائيًا في قائمة عمودية من الصفوف ذات العرض الكامل.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"- انتقِل إلى علامة التبويب التصميم ولاحظ أنّ القيود المضافة قد أدّت إلى توسيع
RecyclerViewلملء المساحة المتاحة.

الخطوة 2: إنشاء تخطيط عنصر القائمة وعنصر نائب لعرض النص
RecyclerView هي مجرد حاوية. في هذه الخطوة، يمكنك إنشاء التنسيق والبنية الأساسية للعناصر التي سيتم عرضها داخل RecyclerView.
للوصول إلى RecyclerView يعمل في أسرع وقت ممكن، يمكنك في البداية استخدام عنصر قائمة بسيط يعرض جودة النوم كرقم فقط. لهذا الغرض، تحتاج إلى عنصر نائب للعرض، TextItemViewHolder. تحتاج أيضًا إلى ملف شخصي، أي TextView، للبيانات. (في خطوة لاحقة، ستتعرّف على المزيد حول عناصر الحافظة وكيفية عرض جميع بيانات النوم).
- أنشئ ملف تخطيط باسم
text_item_view.xml. لا يهمّ العنصر الذي تستخدمه كعنصر أساسي، لأنّك ستستبدل رمز النموذج. - في
text_item_view.xml، احذف كل الرمز البرمجي المحدّد. - أضِف
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" />- فتح "
Util.kt" انتقِل إلى النهاية وأضِف التعريف الموضّح أدناه، ما يؤدي إلى إنشاء الفئةTextItemViewHolder. ضَع الرمز في أسفل الملف، بعد آخر قوس إغلاق. يتم وضع الرمز فيUtil.ktلأنّ عنصر العرض هذا مؤقت، وسيتم استبداله لاحقًا.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)- إذا طُلب منك ذلك، استورِد
android.widget.TextViewوandroidx.recyclerview.widget.RecyclerView.
الخطوة 3: إنشاء SleepNightAdapter
تتمثل المهمة الأساسية في تنفيذ RecyclerView في إنشاء المحوّل. لديك عنصر تحكّم بسيط في العرض لعرض العنصر، وتصميم لكل عنصر. يمكنك الآن إنشاء محوّل. ينشئ المحوّل عنصرًا لعرض البيانات ويملأه ببيانات RecyclerView لعرضها.
- في حزمة
sleeptracker، أنشئ فئة Kotlin جديدة باسمSleepNightAdapter. - اجعل
SleepNightAdapterالصف يمتدRecyclerView.Adapter. يُطلق على الفئة اسمSleepNightAdapterلأنّها تحوّل العنصرSleepNightإلى عنصر يمكن أن يستخدمهRecyclerView. يحتاج المحوّل إلى معرفة عنصر حاوية العرض الذي سيتم استخدامه، لذا عليك إدخالTextItemViewHolder. استورِد المكوّنات اللازمة عند مطالبتك بذلك، وسيظهر لك خطأ لأنّ هناك طرقًا إلزامية يجب تنفيذها.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}- في المستوى الأعلى من
SleepNightAdapter، أنشئ متغيّرlistOfSleepNightلتخزين البيانات.
var data = listOf<SleepNight>()- في
SleepNightAdapter، استبدِلgetItemCount()لعرض حجم قائمة ليالي النوم فيdata. يحتاجRecyclerViewإلى معرفة عدد العناصر التي يتضمّنها المحوّل لعرضها، ويتم ذلك من خلال استدعاءgetItemCount().
override fun getItemCount() = data.size- في
SleepNightAdapter، ألغِ وظيفةonBindViewHolder()، كما هو موضّح أدناه.
يتم استدعاء الدالةonBindViewHolder()من خلالRecyclerViewلعرض بيانات عنصر واحد من القائمة في الموضع المحدّد. لذلك، تأخذ طريقةonBindViewHolder()وسيطتَين: عنصر نائب للعرض وموضع البيانات المطلوب ربطها. في هذا التطبيق، العنصر الحاوي هوTextItemViewHolder، والموضع هو الموضع في القائمة.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}- داخل
onBindViewHolder()، أنشئ متغيّرًا لعنصر واحد في موضع معيّن في البيانات.
val item = data[position]- يحتوي
ViewHolderالذي أنشأته على موقع يُسمّىtextView. داخلonBindViewHolder()، اضبطtextالخاص بـtextViewعلى رقم جودة النوم. لا يعرض هذا الرمز سوى قائمة بالأرقام، ولكن يتيح لك هذا المثال البسيط معرفة كيف يحصل المحوّل على البيانات في حاوية العرض وعلى الشاشة.
holder.textView.text = item.sleepQuality.toString()- في
SleepNightAdapter، يمكنك إلغاء وتنفيذonCreateViewHolder()، الذي يتم استدعاؤه عندما يحتاجRecyclerViewإلى عنصر نائب لعرض عنصر.
تأخذ هذه الدالة مَعلمتَين وتعرضViewHolder. تكون المَعلمةparent، وهي مجموعة العرض التي تحتوي على أداة عرض العرض، دائمًاRecyclerView. يتم استخدام المَعلمةviewTypeعندما تكون هناك طرق عرض متعددة فيRecyclerViewنفسه. على سبيل المثال، إذا وضعت قائمة بعروض نصية وصورة وفيديو فيRecyclerViewنفسه، ستحتاج الدالةonCreateViewHolder()إلى معرفة نوع العرض الذي يجب استخدامه.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}- في
onCreateViewHolder()، أنشئ مثيلاً منLayoutInflater.
تعرف أداة إنشاء التصميم على كيفية إنشاء طرق عرض من تصاميم XML. يحتويcontextعلى معلومات حول كيفية زيادة عدد المشاهدات بشكل صحيح. في أداة ربط لعرض قائمة قابلة لإعادة الاستخدام، عليك دائمًا تمرير سياق مجموعة العرضparent، وهوRecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)- في
onCreateViewHolder()، أنشئviewمن خلال الطلب منlayoutinflaterتضخيمه.
مرِّر التنسيق بتنسيق XML للعرض، ومجموعة العرضparentللعرض. الوسيطة الثالثة، وهي وسيطة منطقية، هيattachToRoot. يجب أن تكون قيمة هذا الوسيطfalse، لأنّRecyclerViewيضيف هذا العنصر إلى بنية العرض الهرمية نيابةً عنك عند الحاجة.
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView- في
onCreateViewHolder()، يمكنك إرجاعTextItemViewHolderتم شراؤه باستخدامview.
return TextItemViewHolder(view)- يجب أن يتيح المحوّل لـ
RecyclerViewمعرفة وقت تغييرdata، لأنّRecyclerViewلا يعرف أي شيء عن البيانات. ولا يعرف سوى عن عناصر الحاوية التي يقدّمها المحوّل.
لإخبارRecyclerViewعندما تتغيّر البيانات التي يعرضها، أضِف أداة ضبط مخصّصة إلى المتغيّرdataفي أعلى فئةSleepNightAdapter. في الدالة الضابطة، امنحdataقيمة جديدة، ثم استدعِnotifyDataSetChanged()لتفعيل إعادة رسم القائمة باستخدام البيانات الجديدة.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}الخطوة 4: إخبار RecyclerView بشأن المحوّل
يجب أن يعرف RecyclerView المحوّل الذي سيتم استخدامه للحصول على عناصر الحاوية.
- فتح "
SleepTrackerFragment.kt" - في
onCreateview()، أنشئ محوّلاً. ضَع هذا الرمز بعد إنشاء نموذجViewModelوقبل عبارةreturn.
val adapter = SleepNightAdapter()- اربط
adapterبـRecyclerView.
binding.sleepList.adapter = adapter- نظِّف مشروعك وأعِد إنشاءه لتعديل العنصر
binding.
إذا استمر ظهور أخطاء حولbinding.sleepListأوbinding.FragmentSleepTrackerBinding، ألغِ صحة ذاكرات التخزين المؤقت وأعِد التشغيل. (اختَر ملف > إبطال ذاكرات التخزين المؤقت / إعادة التشغيل).
إذا شغّلت التطبيق الآن، لن تظهر أي أخطاء، ولكن لن ترى أي بيانات معروضة عند النقر على بدء، ثم إيقاف.
الخطوة 5: نقل البيانات إلى المحوّل
حتى الآن، لديك محوّل وطريقة للحصول على البيانات من المحوّل إلى RecyclerView. عليك الآن نقل البيانات إلى المحوّل من ViewModel.
- فتح "
SleepTrackerViewModel" - ابحث عن المتغيّر
nightsالذي يخزّن جميع ليالي النوم، وهو البيانات المطلوب عرضها. يتم ضبط المتغيّرnightsمن خلال استدعاءgetAllNights()في قاعدة البيانات. - أزِل
privateمنnights، لأنّك ستنشئ مراقبًا يحتاج إلى الوصول إلى هذا المتغيّر. يجب أن يبدو تعريفك على النحو التالي:
val nights = database.getAllNights()- في حزمة
database، افتحSleepDatabaseDao. - ابحث عن الدالة
getAllNights(). لاحظ أنّ هذه الدالة تعرض قائمة بقيمSleepNightعلى شكلLiveData. وهذا يعني أنّ المتغيّرnightsيتضمّنLiveDataالذي يتم تعديله باستمرار من خلالRoom، ويمكنك مراقبةnightsلمعرفة وقت تغييره. - فتح "
SleepTrackerFragment" - في
onCreateView()، أسفل إنشاءadapter، أنشئ مراقبًا للمتغيّرnights.
من خلال توفيرviewLifecycleOwnerللجزء باعتباره مالك دورة الحياة، يمكنك التأكّد من أنّ هذا المراقب لا يكون نشطًا إلا عندما يكونRecyclerViewعلى الشاشة.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})- داخل المراقب، عندما تحصل على قيمة غير فارغة (لـ
nights)، عليك تعيين القيمة إلىdataالخاص بالمحوّل. في ما يلي الرمز البرمجي المكتمل الخاص بالمراقب وإعداد البيانات:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})- إنشاء التعليمات البرمجية وتشغيلها
ستظهر لك أرقام جودة النوم في قائمة إذا كان المحوّل يعمل. تعرض لقطة الشاشة على اليمين القيمة -1 بعد النقر على بدء. تعرض لقطة الشاشة على اليسار رقم جودة النوم المعدَّل بعد النقر على إيقاف واختيار تقييم الجودة.

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

- لحلّ هذه المشكلة، أضِف عبارة
elseلضبط اللون على الأسود إذا لم تكن الجودة أقل من أو تساوي واحدًا.
مع توضيح الشرطين، سيستخدم عنصر الحاوية لون النص الصحيح لكل عنصر.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
} else {
// reset
holder.textView.setTextColor(Color.BLACK) // black
}- شغِّل التطبيق، ويجب أن تكون الأرقام دائمًا باللون الصحيح.
تهانينا! لديك الآن 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 المقدَّم والصِقه.
- أنشئ ملفًا جديدًا لمورد التنسيق وسمِّه
list_item_sleep_night. - استبدِل كل الرمز في الملف بالرمز أدناه. بعد ذلك، تعرَّف على التنسيق الذي أنشأته للتو.
<?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>- انتقِل إلى علامة التبويب التصميم في "استوديو Android". في "طريقة عرض التصميم"، يبدو التخطيط كما في لقطة الشاشة على اليمين أدناه. في عرض المخطط، يبدو مثل لقطة الشاشة على اليسار.

الخطوة 2: إنشاء ViewHolder
- فتح "
SleepNightAdapter.kt" - أنشئ فئة داخل
SleepNightAdapterباسمViewHolderواجعلها توسّعRecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}- داخل
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
- في تعريف
SleepNightAdapter، استخدِمSleepNightAdapter.ViewHolderالذي أنشأته للتو بدلاً منTextItemViewHolder.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {تعديل onCreateViewHolder():
- غيِّر توقيع
onCreateViewHolder()لعرضViewHolder. - غيِّر أداة إنشاء التنسيق لاستخدام مورد التنسيق الصحيح،
list_item_sleep_night. - إزالة البث إلى
TextView - بدلاً من عرض
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():
- غيِّر توقيع
onBindViewHolder()لكي تكون المَعلمةholderمن النوعViewHolderبدلاً منTextItemViewHolder. - داخل
onBindViewHolder()، احذف كل الرموز باستثناء تعريفitem. - حدِّد
valresيحتوي على مرجع إلىresourcesلطريقة العرض هذه.
val res = holder.itemView.context.resources- اضبط نص عرض النص
sleepLengthعلى المدة. انسخ الرمز أدناه، والذي يستدعي دالة تنسيق مضمّنة في الرمز الأولي.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)- يؤدي ذلك إلى حدوث خطأ، لأنّه يجب تحديد
convertDurationToFormatted(). افتحUtil.ktوأزِل التعليق من الرمز وعمليات الاستيراد المرتبطة به. (اختَر الرمز > إضافة تعليق باستخدام تعليقات الأسطر). - في
onBindViewHolder()، استخدِمconvertNumericQualityToString()لضبط الجودة.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)- قد تحتاج إلى استيراد هذه الدوال يدويًا.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString- اضبط الرمز الصحيح للجودة. يتم توفير رمز
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
})- إليك دالة
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
})
}- شغِّل تطبيقك. من المفترض أن تبدو شاشتك مثل لقطة الشاشة أدناه، حيث يظهر رمز جودة النوم، بالإضافة إلى نص يوضّح مدة النوم وجودته.

اكتملت عملية RecyclerView الآن. تعرّفت على كيفية تنفيذ Adapter وViewHolder، وجمّعت بينهما لعرض قائمة تتضمّن RecyclerView Adapter.
يعرض الرمز البرمجي حتى الآن عملية إنشاء محوّل وعنصر نائب للعرض. ومع ذلك، يمكنك تحسين هذا الرمز. يتم الخلط بين الرمز البرمجي لعرض العناصر والرمز البرمجي لإدارة حاويات العرض، وتعرف onBindViewHolder() تفاصيل حول كيفية تعديل ViewHolder.
في تطبيق متاح للجميع، قد يكون لديك عناصر متعدّدة لعرض البيانات، ومحوّلات أكثر تعقيدًا، وعدة مطوّرين يجرون تغييرات. يجب تنظيم الرمز البرمجي بطريقة لا يتضمّن فيها سوى كل ما يتعلّق بعنصر الحاوية.
الخطوة 1: إعادة تصميم onBindViewHolder()
في هذه الخطوة، ستعيد تصميم الرمز البرمجي وتنقل جميع وظائف عنصر نائب العرض إلى ViewHolder. لا يهدف هذا التعديل إلى تغيير شكل التطبيق بالنسبة إلى المستخدم، بل إلى تسهيل عمل المطوّرين على الرمز البرمجي وجعله أكثر أمانًا. لحسن الحظ، يتضمّن "استوديو Android" أدوات للمساعدة في ذلك.
- في
SleepNightAdapter، فيonBindViewHolder()، حدِّد كل شيء باستثناء العبارة التي تحدّد المتغيّرitem. - انقر بزر الماوس الأيمن، ثم اختَر إعادة تصميم > استخراج > دالة.
- سمِّ الدالة
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
})
}- ضَع المؤشر على الكلمة
holderمن المَعلمةholderالخاصة بـbind(). اضغط علىAlt+Enter(Option+Enterعلى جهاز Mac) لفتح قائمة "النية". اختَر تحويل المَعلمة إلى متلقّي لتحويلها إلى دالة إضافة تتضمّن التوقيع التالي:
private fun ViewHolder.bind(item: SleepNight) {...}- قُص الدالة
bind()والصِقها فيViewHolder. - اجعل
bind()علنيًا. - استورِد
bind()إلى المحوّل إذا لزم الأمر. - بما أنّها أصبحت الآن في
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.
- في
onCreateViewHolder()، اختَر كل الرمز في نص الدالة. - انقر بزر الماوس الأيمن، ثم اختَر إعادة تصميم > استخراج > دالة.
- سمِّ الدالة
fromواقبل المَعلمات المقترَحة. انقر على حسنًا. - ضَع المؤشر على اسم الدالة
from. اضغط علىAlt+Enter(Option+Enterعلى جهاز Mac) لفتح قائمة "النية". - انقر على الانتقال إلى العنصر المرافق. يجب أن تكون الدالة
from()في عنصر مصاحب حتى يمكن استدعاؤها في الفئةViewHolder، وليس في مثيلViewHolder. - انقل العنصر
companionإلى الفئةViewHolder. - اجعل
from()علنيًا. - في
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)
}
}- غيِّر توقيع الفئة
ViewHolderلكي يكون الدالة الإنشائية خاصة. بما أنّfrom()أصبحت الآن طريقة تعرض مثيلاً جديدًا منViewHolder، لم يعُد هناك أي سبب يدعو أي شخص إلى استدعاء الدالة الإنشائية لـViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){- شغِّل التطبيق. من المفترض أن يتم إنشاء التطبيق وتشغيله كما كان من قبل، وهو النتيجة المطلوبة بعد إعادة البناء.
مشروع "استوديو Android": RecyclerViewFundamentals
- يُعد عرض قائمة أو شبكة من البيانات إحدى مهام واجهة المستخدم الأكثر شيوعًا في Android. تم تصميم
RecyclerViewليكون فعّالاً حتى عند عرض قوائم كبيرة للغاية. RecyclerViewلا تنفّذ سوى العمل اللازم لمعالجة العناصر أو رسمها التي تظهر حاليًا على الشاشة.- عندما يتم تمرير عنصر خارج الشاشة، تتم إعادة استخدام طرق العرض الخاصة به. وهذا يعني أنّ العنصر يتضمّن محتوًى جديدًا يتم تمريره على الشاشة.
- يساعد نمط المحوّل في هندسة البرامج على عمل عنصر مع واجهة برمجة تطبيقات أخرى. تستخدم
RecyclerViewمحوّلاً لتحويل بيانات التطبيق إلى تنسيق يمكنها عرضه، بدون الحاجة إلى تغيير طريقة تخزين التطبيق للبيانات ومعالجتها.
لعرض بياناتك في RecyclerView، تحتاج إلى الأجزاء التالية:
- RecyclerView
لإنشاء مثيل منRecyclerView، حدِّد عنصر<RecyclerView>في ملف التصميم. - LayoutManager
يستخدمRecyclerViewLayoutManagerلتنظيم تخطيط العناصر في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.
ابدأ الدرس التالي: