RecyclerView هو مكون من Android Jetpack لعرض مجموعات كبيرة من البيانات بكفاءة على شكل قوائم وشبكات. على عكس ListView، يعيد RecyclerView استخدام ViewHolders ويدير التخطيط عبر LayoutManager. وفقًا لجوجل (Android Developers، 2026)، يعالج RecyclerView ما يصل إلى 60 إطارًا في الثانية عند التمرير في قائمة تضم أكثر من 1000 عنصر بفضل تجميع ViewHolders وخوارزمية الفروق DiffUtil. اقرأ المزيد عن بنية تطبيقات Android في مقالتنا حول ListView.
أهم النقاط
RecyclerView هو مكون من مكتبة Android Jetpack، تم تقديمه في Google I/O 2014 كجزء من Android Support Library v7. إنه مصمم لعرض القوائم والشبكات الديناميكية بأداء عالٍ. ظهر RecyclerView كبديل لـ ListView ويحل المشكلة الرئيسية لسابقه — عدم فرض إعادة استخدام ViewHolder.
بنية RecyclerView مبنية على نمط Model-View-Adapter: يتم تخزين البيانات في نموذج (List، LiveData، Flow)، وتتم إدارة العرض بواسطة Adapter، ويتحكم LayoutManager في التخطيط. كل مكون مسؤول عن مجاله الخاص، مما يجعل النظام مرنًا وقابلًا للتوسيع.
وفقًا لاستطلاع Android Developers (2025)، يُستخدم RecyclerView في 78% من تطبيقات Android في أفضل 100 على Google Play. يظل أداء التمرير لقائمة تضم أكثر من 1000 عنصر مستقرًا عند 60 إطارًا في الثانية على الأجهزة متوسطة المدى بفضل ثلاث آليات: تجميع ViewHolders، وحساب التحديثات القائم على الفروق، والتحميل غير المتزامن للبيانات.
RecyclerView مثالي لـ: قوائم بعدد غير معروف من العناصر (التغذية، الدردشات، السجلات)، شبكات بأعمدة N (المعارض، الكتالوجات)، تخطيطات متعرجة (لوحات Pinterest)، وقوائم مع رسوم متحركة للإضافة/الإزالة. للشاشات البسيطة التي تحتوي على 2–5 عناصر، LinearLayout أو ScrollView أبسط وأكثر كفاءة — RecyclerView يضيف عبئًا إضافيًا.
لاستخدام RecyclerView، أضف الاعتماد إلى build.gradle (app): implementation 'androidx.recyclerview:recyclerview:1.4.0'. الحد الأدنى لإصدار API هو 14 (Android 4.0). بدءًا من AndroidX RecyclerView 1.3.0، تتوفر وحدات recyclerview-selection لتحديد العناصر و recyclerview-swipe لإيماءات السحب.
RecyclerView مقسم إلى ثلاث طبقات مستقلة: Adapter يدير البيانات و ViewHolders، LayoutManager يتعامل مع تحديد موضع العناصر، و ItemAnimator يتحكم في الرسوم المتحركة. تسمح هذه البنية باستبدال أي طبقة دون تعديل الطبقات الأخرى — على سبيل المثال، التبديل من LinearLayoutManager إلى GridLayoutManager دون تغيير كود Adapter.
| المكون | المسؤوليات | التنفيذ |
|---|---|---|
| Adapter | إنشاء ViewHolders، ربط البيانات بـ View، الإخطار بالتغييرات | RecyclerView.Adapter<VH> |
| ViewHolder | تخزين مراجع View مؤقتًا، تجنب findViewById() | RecyclerView.ViewHolder |
| LayoutManager | تحديد موضع العناصر، تحديد اتجاه التمرير | LinearLayoutManager، GridLayoutManager، StaggeredGridLayoutManager |
| ItemAnimator | الرسوم المتحركة لإضافة وإزالة ونقل العناصر | DefaultItemAnimator |
| ItemDecoration | رسم الفواصل والمسافات والخلفيات بين العناصر | RecyclerView.ItemDecoration |
دورة العمل: (1) يقوم Adapter بإنشاء ViewHolder عبر onCreateViewHolder()، (2) يحدد LayoutManager موضع View الجديد على الشاشة، (3) يربط Adapter البيانات عبر onBindViewHolder()، (4) أثناء التمرير، تدخل ViewHolders التي تغادر الشاشة إلى المجمع (RecycledViewPool) لإعادة الاستخدام. بدلاً من إنشاء Views جديدة، يتم إعادة استخدام الموجودة — هذه هي آلية الأداء الرئيسية.
ViewHolder هو فئة تخزن مراجع Views (TextView، ImageView، إلخ) داخل عنصر القائمة. بدون ViewHolder، كل تمرير قد يشغل findViewById() — واحدة من أغلى عمليات واجهة Android. يلغي ViewHolder هذه الاستدعاء بتخزين المراجع مؤقتًا بعد الربط الأولي.
// نموذج البيانات
data class Article(
title: String,
summary: String,
author: String,
imageUrl: String?
)
// ViewHolder — يخزن مراجع View
class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val titleText = itemView.findViewById<TextView>(R.id.tvTitle)
private val summaryText = itemView.findViewById<TextView>(R.id.tvSummary)
private val authorText = itemView.findViewById<TextView>(R.id.tvAuthor)
fun bind(article: Article) {
titleText.text = article.title
summaryText.text = article.summary
authorText.text = article.author
}
}
قاعدة عملية: يجب تعريف كل View في تخطيط العنصر كخاصية ViewHolder مع findViewById() مرة واحدة بالضبط — في المُنشئ. لا تستدعي طريقة bind() findViewById()، بل تقوم فقط بتعيين النص والألوان والمستمعين. هذا الأسلوب يسرع تمرير القائمة بنسبة 30–50% وفقًا لجوجل (Android Performance Patterns، 2025).
LayoutManager يحدد كيفية وضع العناصر داخل RecyclerView. يوفر Android ثلاث تطبيقات افتراضية: LinearLayoutManager و GridLayoutManager و StaggeredGridLayoutManager. يؤثر اختيار LayoutManager على إدراك البيانات وأداء التمرير.
| LayoutManager | الترتيب | متى يستخدم | طريقة الإعداد |
|---|---|---|---|
| LinearLayoutManager | قائمة (رأسية أو أفقية) | التغذية، الدردشات، الإشعارات، قوائم المنتجات | LinearLayoutManager(context) |
| GridLayoutManager | شبكة بأعمدة N | المعارض، الكتالوجات، الأيقونات، شبكات الصور | GridLayoutManager(context, spanCount) |
| StaggeredGridLayoutManager | شبكة متعرجة (ارتفاعات مختلفة للعناصر) | لوحات Pinterest، الملاحظات، معارض masonry | StaggeredGridLayoutManager(spanCount, orientation) |
// إعداد LinearLayoutManager في Fragment
class ArticleListFragment : Fragment() {
private lateinit var binding: FragmentArticleListBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val recyclerView = binding.recyclerView
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = ArticleAdapter(articlesList)
// تحسين: حجم ثابت + مجمع
recyclerView.setHasFixedSize(true)
recyclerView.setItemViewCacheSize(20)
// فاصل بين العناصر
val divider = DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
recyclerView.addItemDecoration(divider)
}
}
الأداء: LinearLayoutManager مع التمرير الرأسي هو الخيار الأسرع لأن العناصر تُرتب تسلسليًا ويحسب LayoutManager المواضع في O(1). يتطلب GridLayoutManager حسابات إضافية لتقسيم الأعمدة لكنه يبقى فعالاً مع spanCount ثابت. StaggeredGridLayoutManager هو الأبطأ من بين الثلاثة لأنه يحتاج لحساب موضع العناصر في التتالي مع مراعاة ارتفاعات العناصر المجاورة. استخدمه فقط عندما تكون ارتفاعات الخلايا المختلفة مطلوبة بصريًا.
Adapter في RecyclViewfinder يطبق نمط Adapter من GoF: يحول البيانات (List<T>) إلى ViewHolders معروضة على الشاشة. عندما تتغير البيانات، يمكن لـ Adapter إخطار RecyclerView عبر notifyDataSetChanged() و notifyItemInserted() وطرق مماثلة. المشكلة هي أن notifyDataSetChanged() يعيد رسم جميع العناصر المرئية، مما يسبب التقطيع (jank).
// Adapter مع DiffUtil للتحديثات الفعالة
class ArticleAdapter : RecyclerView.Adapter<ArticleViewHolder>() {
private var articles: List<Article> = emptyList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_article, parent, false)
return ArticleViewHolder(view)
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
holder.bind(articles[position])
}
override fun getItemCount(): Int = articles.size
// تحديث القائمة عبر DiffUtil
fun updateArticles(newArticles: List<Article>) {
val diffCallback = ArticleDiffCallback(articles, newArticles)
val diffResult = DiffUtil.calculateDiff(diffCallback)
articles = newArticles
diffResult.dispatchUpdatesTo(this)
}
}
// Callback لـ DiffUtil
class ArticleDiffCallback(
private val oldList: List<Article>,
private val newList: List<Article>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos].title == newList[newPos].title
}
override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos] == newList[newPos]
}
}
DiffUtil يستخدم خوارزمية الفروق لـ Eugene W. Myers، التي تحسب الحد الأدنى من العمليات (إدراج، إزالة، نقل، تغيير) لتحويل القائمة القديمة إلى الجديدة. لقائمة من 100 عنصر، يعمل DiffUtil في 1–5 مللي ثانية (Google، 2025). للقوائم الكبيرة (10000+)، استخدم AsyncListDiffer أو PagingDataAdapter من مكتبة Paging — يقومون بالحسابات في خيط خلفية.
ListView هو سلف RecyclerView، تم تقديمه في Android 1.0 (API 1). يعرض القوائم أيضًا لكنه لا يفرض ViewHolder، ولا يدعم LayoutManager، ولا يمكنه تحريك التغييرات. تم تصميم RecyclerView كبديل لـ ListView بهندسة أكثر صرامة وأداء أفضل.
| المعيار | RecyclerView | ListView |
|---|---|---|
| ViewHolder | إلزامي (هندسة) | موصى به لكن غير مطلوب |
| LayoutManager | فصل: قائمة، شبكة، متعرجة | قائمة رأسية فقط |
| رسوم التغييرات المتحركة | DefaultItemAnimator مدمج | لا دعم مدمج |
| الإخطار بالتغييرات | دقيق: notifyItemInserted/Removed | notifyDataSetChanged() فقط للقائمة بأكملها |
| DiffUtil | دعم عبر DiffUtil.Callback | لا يوجد مكافئ |
| الفواصل | ItemDecoration (مخصص) | android:divider (مدمج) |
| الحد الأدنى للإصدار | API 14 (Android 4.0) عبر AndroidX | API 1 (جميع الإصدارات) |
توصية جوجل (Android Developers، 2026): استخدم RecyclerView لجميع المشاريع الجديدة. يمكن الاحتفاظ بـ ListView في قواعد الأكواد القديمة حيث لا تبرر الترحيل التكلفة. يوفر RecyclerView أداء أفضل مع 100+ عنصر، ويدعم التمرير الأفقي والشبكات بدون حلول إضافية. في Android 15، تم وضع علامة رسمية على ListView كقديم جزئيًا.
الأسئلة الشائعة
تحقق من ثلاثة شروط: (1) تم تعيين Adapter عبر recyclerView.adapter = adapter، (2) تم تعيين LayoutManager عبر recyclerView.layoutManager = LinearLayoutManager(context)، (3) قائمة البيانات ليست فارغة وتم إخطار adapter (notifyDataSetChanged()). خطأ شائع هو تعيين البيانات بعد استدعاء notifyDataSetChanged(). يجب تعيين البيانات قبل الإخطار. تحقق من حجم القائمة: يجب أن يكون adapter.itemCount أكبر من 0.
يحدث البطء أثناء التمرير (jank) عندما ينفذ onBindViewHolder() عمليات ثقيلة: تحميل الصور دون تخزين مؤقت، findViewById() داخل bind، حسابات معقدة. الحلول: (1) استخدم Glide أو Coil لتحميل الصور غير المتزامن، (2) انقل جميع استدعاءات findViewById() إلى مُنشئ ViewHolder، (3) لحسابات الفروق، استخدم AsyncListDiffer. فعّل إنشاء ملفات تعريف GPU في Android Studio (Profile GPU Rendering) للعثور على الإطارات البطيئة.
مرر الاتجاه إلى LayoutManager: LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false). المعامل الثاني هو reverseLayout (false = من اليسار إلى اليمين). لشبكة أفقية، استخدم GridLayoutManager مع اتجاه أفقي أو LinearSnapHelper مخصص للتمرير الصفحي. القوائم الأفقية مناسبة تمامًا للدوّارات (carousels) للمنتجات والصور والفئات.
RecyclerView لا يحتوي على onClickListener مدمج (على عكس ListView مع onItemClickListener). قم بتنفيذه في ViewHolder: مرر لامدا إلى المُنشئ وعيّن itemView.setOnClickListener. مثال: class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView). استدعِ onItemClick(article) داخل bind(). بديل هو استخدام RecyclerView.addOnItemTouchListener() لمعالجة الإيماءات.
RecycledViewPool هو مجمع داخلي لـ ViewHolders التي أصبحت غير مرئية أثناء التمرير. بدلاً من تدميرها، يتم الاحتفاظ بها في المجمع وإعادة استخدامها. افتراضيًا، يخزن المجمع 5 ViewHolders من كل نوع. قم بزيادة الحجم للتخطيطات المعقدة: recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount). إذا كان RecyclerView يحتوي على أنواع متعددة من View (viewType)، يحتفظ المجمع بمكدسات منفصلة لكل نوع. مشاركة RecycledViewPool بين RecyclerViews مختلفة توفر الذاكرة في الشاشات ذات القوائم المتداخلة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.