RecyclerView — مؤلفه Android Jetpack برای نمایش کارآمد مجموعههای بزرگ داده به صورت لیست و شبکه. برخلاف ListView، RecyclerView ViewHolderها را بازیافت میکند و چیدمان را از طریق LayoutManager مدیریت میکند. طبق گفته Google (Android Developers, 2026)، RecyclerView هنگام اسکرول لیست 1000+ عنصر به لطف استخر ViewHolderها و الگوریتم تفاوت DiffUtil تا 60 فریم در ثانیه پردازش میکند. درباره معماری برنامههای 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 FPS در دستگاههای میانرده به لطف سه مکانیسم پایدار میماند: استخر ViewHolder، محاسبه تفاوت بهروزرسانیها و بارگذاری ناهمگام داده.
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 دادهها و ViewHolderها را مدیریت میکند، LayoutManager مسئول موقعیتیابی عناصر است، ItemAnimator انیمیشنها را کنترل میکند. چنین معماری اجازه میدهد هر لایه بدون تغییر لایههای دیگر جایگزین شود — مثلاً از LinearLayoutManager به GridLayoutManager بدون تغییر کد Adapter تغییر دهید.
| مؤلفه | وظایف | پیادهسازی |
|---|---|---|
| Adapter | ایجاد ViewHolder، اتصال داده به 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) هنگام اسکرول، ViewHolderهایی که از صفحه خارج شدهاند برای استفاده مجدد به استخر (RecycledViewPool) میروند. به جای ایجاد View جدید، از Viewهای موجود استفاده مجدد میشود — این مکانیسم کلیدی عملکرد است.
ViewHolder — کلاسی است که ارجاعات به Viewها (TextView, ImageView و غیره) را در داخل عنصر لیست ذخیره میکند. بدون ViewHolder در هر اسکرول سیستم findViewById() — یکی از گرانترین عملیات Android UI — را فراخوانی میکرد. 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() را فراخوانی نمیکند، فقط متن، رنگها و شنوندگان را تنظیم میکند. این رویکرد طبق Google (Android Performance Patterns, 2025) اسکرول لیست را 30-50% تسریع میکند.
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 در RecyclerView الگوی Adapter از GoF را پیادهسازی میکند: دادهها (List<T>) را به ViewHolderهای نمایش داده شده روی صفحه تبدیل میکند. هنگام تغییر داده، 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 difference algorithm استفاده میکند که حداقل تعداد عملیات (insert, remove, move, change) برای تبدیل لیست قدیم به جدید را محاسبه میکند. برای لیست 100 عنصری DiffUtil در 1-5 ms کار میکند (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 (همه نسخهها) |
توصیه Google (Android Developers, 2026): برای همه پروژههای جدید از RecyclerView استفاده کنید. ListView را میتوان در پایگاههای کد قدیمی (legacy) نگه داشت جایی که مهاجرت توجیهپذیر نیست. 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 سفارشی برای اسکرول صفحهای استفاده کنید. لیستهای افقی برای چرخدنده محصولات، تصاویر، دستهبندیها مناسب هستند.
RecyclerView onClickListener داخلی ندارد (برخلاف ListView با onItemClickListener). آن را در ViewHolder پیادهسازی کنید: lambda را به سازنده منتقل کنید و itemView.setOnClickListener را تنظیم کنید. مثال: class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView). درون bind() onItemClick(article) را فراخوانی کنید. جایگزین — RecyclerView.addOnItemTouchListener() را برای مدیریت ژستها تنظیم کنید.
RecycledViewPool — استخر داخلی ViewHolderهایی است که هنگام اسکرول نامرئی شدهاند. به جای نابودی، در استخر ذخیره و مجدداً استفاده میشوند. استخر به طور پیشفرض 5 ViewHolder از هر نوع را ذخیره میکند. برای چیدمانهای پیچیده اندازه را افزایش دهید: recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount). اگر RecyclerView چند نوع View داشته باشد (viewType)، استخر پشتههای جداگانه برای هر کدام ذخیره میکند. استفاده مشترک از RecycledViewPool بین RecyclerViewهای مختلف در صفحات با لیستهای تودرتو حافظه را ذخیره میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید