LazyColumn — چیست، لیست‌های کارآمد در Compose

نویسنده: IT Sectr منتشر شده: 2026-02-24 زمان مطالعه: 10 دقیقه

LazyColumn — کامپوننت Jetpack Compose برای نمایش لیست‌های قابل پیمایش با بارگذاری تنبل. برخلاف RecyclerView، LazyColumn ViewHolder ایجاد نمی‌کند و از XML استفاده نمی‌کند — همه عناصر به صورت اعلامی از طریق توابع composable توصیف می‌شوند. نشان می‌دهیم LazyColumn چیست، LazyListState و items API چگونه کار می‌کنند و چگونه به عملکرد 60 FPS دست یابیم. در IT Sectr ما از LazyColumn در تمام پروژه‌های جدید Compose استفاده می‌کنیم. برای مقایسه با رویکرد کلاسیک مقاله RecyclerView را بخوانید.

نکات کلیدی

  • LazyColumn — کامپوننت Jetpack Compose برای لیست‌های عمودی با بارگذاری تنبل، بخشی از Material Design 3.
  • LazyListState — وضعیت لیست: موقعیت پیمایش، عناصر قابل مشاهده، پیمایش برنامه‌ریزی شده.
  • items API — توابع items()، itemsIndexed()، items() با کلیدهای اتصال داده.
  • key — پارامتری برای شناسایی پایدار عناصر که به Compose اجازه استفاده مجدد از ترکیب را می‌دهد.
  • LazyVerticalGrid — نسخه LazyColumn برای شبکه‌هایی با تعداد ستون ثابت.

LazyColumn در Jetpack Compose چیست؟

LazyColumn — تابع composable از کتابخانه Foundation Compose است که یک لیست عمودی از عناصر را با بارگذاری تنبل (lazy) نمایش می‌دهد: فقط عناصری که روی صفحه قابل مشاهده هستند یا در نزدیکی آن قرار دارند (منطقه prefetch) ایجاد و ترکیب می‌شوند. LazyColumn در Compose 1.0 (2021) همراه با LazyRow (لیست افقی) و LazyVerticalGrid (شبکه) ظاهر شد.

بارگذاری تنبل از طریق مکانیزم SubcomposeLayout کار می‌کند: LazyColumn فضای موجود را اندازه‌گیری می‌کند، محدوده قابل مشاهده را از LayoutInfo درخواست می‌کند و فقط عناصر موجود در این محدوده را ترکیب می‌کند. عناصری که از صفحه خارج شده‌اند، ترکیب را ترک می‌کنند (به جز بافر prefetch). طبق داده‌های Google (Android Performance، 2026)، LazyColumn هنگام پیمایش لیستی از 10000+ عنصر در دستگاه‌های میان‌رده، 60 FPS را تضمین می‌کند.

تفاوت با Column + ScrollState

Column با پیمایش عمودی — چیدمان معمولی همه عناصر یکی پس از دیگری با اعمال modifier verticalScroll است. Column تنبل نیست: همه عناصر فرزند بلافاصله ترکیب می‌شوند، حتی اگر قابل مشاهده نباشند. برای لیست 200+ عنصر، Column تأخیر قابل توجهی در شروع ایجاد می‌کند. LazyColumn فقط عناصر قابل مشاهده + بافر (به طور پیش‌فرض 1 صفحه جلو/عقب) را ترکیب می‌کند که آن را برای لیست‌های طولانی ضروری می‌کند.

items API: اتصال داده‌ها به لیست

items API — مجموعه توابع extension برای LazyColumn که یک لیست داده و توصیفگر DSL برای هر عنصر دریافت می‌کنند. توابع اصلی: items(count, key, itemContent) برای تعداد ثابت، items(list, key, itemContent) برای لیست، itemsIndexed(list, key, itemContent) با ایندکس. پارامتر key — شرط اجباری برای عملکرد — یک شناسه پایدار عنصر به Compose می‌دهد.

kotlin
// LazyColumn پایه با itemContent
@Composable
fun ArticleList(articles: List<Article>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        // Header — تنبل نیست، همیشه در ابتدا
        item {
            Text("آخرین مقالات", style = MaterialTheme.typography.headlineMedium)
        }

        // لیست مقالات با کلید بر اساس id
        items(articles, key = { it.id }) { article ->
            ArticleCard(
                title = article.title,
                summary = article.summary,
                onClick = { onArticleClick(article.id) }
            )
        }

        // Footer با نشانگر بارگیری
        item {
            CircularProgressIndicator(
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            )
        }
    }
}

// عنصر سفارشی لیست
@Composable
fun ArticleCard(title: String, summary: String, onClick: () -> Unit) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .clickable(onClick = onClick)
    ) {
        Column(modifier = Modifier.padding(12.dp)) {
            Text(text = title, style = MaterialTheme.typography.titleMedium)
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = summary, style = MaterialTheme.typography.bodyMedium)
        }
    }
}

item {} vs items(): برای عناصر تکی که همیشه در لیست حضور دارند (header، footer، جداکننده‌ها) از item {} استفاده کنید. برای لیست داده پویا از items() استفاده کنید. items() Iterable یا Int و یک بلوک DSL که برای هر عنصر فراخوانی می‌شود را می‌پذیرد. item و items را در یک LazyColumn ترکیب کنید: ترتیب فراخوانی‌ها ترتیب نمایش را تعیین می‌کند.

LazyListState: مدیریت موقعیت پیمایش

LazyListState — شیء ذخیره‌کننده وضعیت لیست: موقعیت فعلی پیمایش (firstVisibleItemIndex، firstVisibleItemScrollOffset)، عناصر قابل مشاهده (layoutInfo) و روش‌های پیمایش برنامه‌ریزی شده (scrollToItem، animateScrollToItem). LazyListState از طریق rememberLazyListState() ایجاد می‌شود و از طریق پارامتر state به LazyColumn منتقل می‌شود.

kotlin
// LazyColumn با حفظ موقعیت پیمایش
@Composable
fun ScrollingList(items: List<Item>) {
    val listState = rememberLazyListState()

    Box(modifier = Modifier.fillMaxSize()) {
        LazyColumn(state = listState) {
            items(items, key = { it.id }) { item ->
                ListItemView(item = item)
            }
        }

        // دکمه "به بالا" بعد از دهمین عنصر ظاهر می‌شود
        val showButton by remember {
            derivedStateOf { listState.firstVisibleItemIndex > 10 }
        }

        AnimatedVisibility(visible = showButton) {
            FloatingActionButton(
                modifier = Modifier
                    .align(Alignment.BottomEnd)
                    .padding(16.dp),
                onClick = {
                    coroutineScope.launch {
                        listState.animateScrollToItem(0)
                    }
                }
            ) {
                Icon(Icons.Default.KeyboardArrowUp, "برو به بالا")
            }
        }
    }
}

// دریافت اطلاعات عناصر قابل مشاهده
@Composable
fun ListDebugInfo(listState: LazyListState) {
    val visibleItems = listState.layoutInfo.visibleItemsInfo
    val totalItems = listState.layoutInfo.totalItemsCount
    val firstItem = visibleItems.firstOrNull()?.index
    val lastItem = visibleItems.lastOrNull()?.index

    Text("$firstItem–$lastItem از $totalItems نمایش داده شده")
}

پیمایش برنامه‌ریزی شده از طریق توابع suspend لیستState انجام می‌شود: scrollToItem(index, scrollOffset) — جابجایی فوری (بدون انیمیشن)، animateScrollToItem(index) — با انیمیشن. برای پیمایش از کوروتین از LaunchedEffect یا coroutineScope.launch استفاده کنید. مهم: توابع suspend را از داخل ترکیب (داخل @Composable) فراخوانی نکنید — از لامبداها (onClick، LaunchedEffect) استفاده کنید.

LazyColumn vs RecyclerView: مقایسه رویکردها

LazyColumn و RecyclerView یک وظیفه را حل می‌کنند — نمایش کارآمد لیست‌های طولانی. تفاوت در معماری است: RecyclerView از ViewHolder و Adapter (Android View کلاسیک) استفاده می‌کند، LazyColumn — توابع composable اعلامی بدون XML و ViewHolder. انتخاب به stack فناوری پروژه بستگی دارد: Compose vs View-based UI.

پارامترLazyColumn (Compose)RecyclerView (View)
چیدمانتوابع Composable (Kotlin DSL)فایل‌های XML + ViewBinding
آداپتورitems() / itemsIndexed() DSLRecyclerView.Adapter + ViewHolder
مرتب‌سازی/فیلترSnapshot state + derivedStateOfDiffUtil + AsyncListDiffer
انیمیشنAnimatedVisibility + Modifier.animateItemDefaultItemAnimator
انواع مختلف عناصرitem {}/items() + when بر اساس نوعgetItemViewType + چندین ViewHolder
پیمایش به موقعیتlistState.animateScrollToItem()layoutManager.scrollToPosition()
PrefetchIn-process (بافر SubcomposeLayout)RecycledViewPool + GapWorker
شبکه/صفحه‌بندیcollectAsLazyPagingItems() + Paging 3PagingDataAdapter + Paging 3

توصیه Google (Android Developers، 2026): برای پروژه‌های جدید روی Jetpack Compose از LazyColumn استفاده کنید. برای پروژه‌های View-based از RecyclerView استفاده کنید. LazyColumn نیاز به Compose دارد که حدود 3–5 مگابایت به APK اضافه می‌کند — هنگام پشتیبانی از دستگاه‌های قدیمی این را در نظر بگیرید. اگر پروژه از سیستم View استفاده می‌کند، LazyColumn بدون AndroidView نمی‌تواند در یک صفحه با چیدمان XML共存 داشته باشد — در چنین مواردی ماندن روی RecyclerView ساده‌تر است.

LazyVerticalGrid و LazyHorizontalGrid: شبکه‌ها

LazyVerticalGrid — نسخه LazyColumn برای شبکه‌هایی با تعداد ستون ثابت (columns). LazyHorizontalGrid — شبکه افقی با تعداد ردیف ثابت (rows). هر دو کامپوننت از همان اصول بارگذاری تنبل و همان items API مشابه LazyColumn استفاده می‌کنند. پارامتر columns تعداد ستون‌ها را از طریق GridCells.Fixed(N) یا GridCells.Adaptive(minSize) تعیین می‌کند.

kotlin
// شبکه با ستون‌های تطبیقی
@Composable
fun PhotoGrid(photos: List<Photo>) {
    LazyVerticalGrid(
        columns = GridCells.Adaptive(minSize = 128.dp),
        contentPadding = PaddingValues(8.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(photos, key = { it.id }) { photo ->
            AsyncImage(
                model = photo.url,
                contentDescription = photo.title,
                modifier = Modifier.aspectRatio(1f)
            )
        }
    }
}

// لیست افقی (LazyRow)
@Composable
fun CategoryCarousel(categories: List<Category>) {
    LazyRow(
        contentPadding = PaddingValues(horizontal = 16.dp),
        horizontalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(categories, key = { it.id }) { category ->
            FilterChip(
                selected = category.isSelected,
                onClick = { onCategoryClick(category.id) },
                label = { Text(category.name) }
            )
        }
    }
}

GridCells.Adaptive به طور خودکار تعداد ستون‌ها را بر اساس حداقل اندازه سلول محاسبه می‌کند. به عنوان مثال، GridCells.Adaptive(128.dp) در صفحه‌ای با عرض 360 dp، 2 ستون و در تبلت 600 dp، 4 ستون قرار می‌دهد. GridCells.Fixed(2) همیشه دقیقاً 2 ستون نشان می‌دهد. Adaptive برای تطبیق چندصفحه‌ای ترجیح داده می‌شود — بدون نیاز به کوئری‌های رسانه‌ای به طور خودکار با عرض صفحه تنظیم می‌شود.

عملکرد LazyColumn: key، contentType، prefetch

عملکرد LazyColumn به سه عامل بستگی دارد: پایداری کلیدها (key)، نوع محتوا (contentType) و بافر prefetch (beyondBounds). به طور پیش‌فرض LazyColumn 1 صفحه جلو و 1 صفحه عقب را بافر می‌کند. Google (Android Developers، 2026) توصیه می‌کند این پارامترها را برای لیست‌هایی با انواع مختلف عناصر یا ترکیب پیچیده پیکربندی کنید.

پارامترتوضیحاتتوصیه
keyشناسه پایدار عنصرهمیشه key = { it.id } را مشخص کنید. بدون کلید Compose از ایندکس استفاده می‌کند و جابجایی عناصر انیمیشن را خراب می‌کند.
contentTypeنوع محتوا برای جداسازی استخرهابرای لیست‌هایی با انواع مختلف عناصر (متن + تصویر + تبلیغات) مشخص کنید. Compose فقط در داخل یک contentType از ترکیب مجدد استفاده می‌کند.
beyondBoundsتعداد صفحات برای prefetchبرای لیست‌هایی با تصاویر سنگین به 2–3 افزایش دهید. پیش‌فرض 1 صفحه.
Modifier.animateItemانیمیشن تغییر موقعیتبرای مرتب‌سازی و فیلتر انیمیشنی استفاده کنید (Compose 1.7+).
kotlin
// LazyColumn بهینه‌سازی شده با contentType و prefetch
@Composable
fun OptimizedFeed(items: List<FeedItem>) {
    val listState = rememberLazyListState()

    LazyColumn(
        state = listState,
        modifier = Modifier.fillMaxSize(),
        beyondBoundsPageCount = 2  // prefetch 2 صفحه
    ) {
        items(
            items = items,
            key = { it.id },
            contentType = { it.type }  // جداسازی بر اساس انواع
        ) { item ->
            when (item) {
                is FeedItem.Post -> PostView(item)
                is FeedItem.Ad -> AdView(item)
                is FeedItem.Suggested -> SuggestedView(item)
            }
        }
    }
}

// اندازه‌گیری عملکرد از طریق listState
@Composable
fun ScrollPerformance(listState: LazyListState) {
    val scrollInfo = listState.layoutInfo
    val visibleCount = scrollInfo.visibleItemsInfo.size
    val total = scrollInfo.totalItemsCount

    // derivedStateOf — اگر مقدار تغییر نکرده باشد دوباره ترکیب نمی‌شود
    val scrollProgress by remember {
        derivedStateOf {
            if (total > 0) visibleCount.toFloat() / total else 0f
        }
    }

    LinearProgressIndicator(
        progress = scrollProgress,
        modifier = Modifier.fillMaxWidth().height(2.dp)
    )
}

خطاهای رایج: (1) نبود key — عناصر با هر تغییر لیست دوباره ترکیب می‌شوند؛ (2) استفاده از mutableStateListOf بدون جریان‌های snapshot — تغییرات ممکن است ردیابی نشوند؛ (3) فراخوانی توابع suspend در داخل itemContent — ترکیب را قطع می‌کند؛ (4) نبود contentType برای انواع مختلط — Compose ترکیب تبلیغات را برای پست متنی استفاده مجدد می‌کند و مصنوعات بصری ایجاد می‌کند. در IT Sectr ما contentType را به همه لیست‌هایی با سه نوع محتوا یا بیشتر اضافه می‌کنیم.

سوالات متداول

LazyColumn به کندی پیمایش می‌کند — چه باید کرد؟

سه پارامتر را بررسی کنید: (1) key — بدون کلید پایدار Compose نمی‌تواند از ترکیب مجدد استفاده کند، (2) contentType — برای لیست‌هایی با انواع مختلف عناصر، (3) beyondBoundsPageCount — برای تصاویر به 2 افزایش دهید. محاسبات سنگین داخل itemContent را با کلید داده به remember منتقل کنید. برای گرافیک ساده به جای Image از Modifier.drawWithContent استفاده کنید.

چگونه بین عناصر LazyColumn جداکننده اضافه کنیم؟

برای فاصله بین عناصر از LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) استفاده کنید. برای جداکننده بصری با خط، item { Divider() } را بین items اضافه کنید. جداکننده خودکار: items(items, key = { it.id }) { item -> ... }، و بین هر عنصر item { HorizontalDivider() } را وارد کنید. برای این کار از LazyListScope.items(list, key) { /* عنصر */ } و جداگانه LazyListScope.item { Divider() } استفاده کنید.

LazyColumn داخل Column کار نمی‌کند — چرا؟

LazyColumn به طور پیش‌فرض ارتفاع نامتناهی (fillMaxSize) دارد. داخل Column بدون ارتفاع ثابت، LazyColumn نمی‌تواند اندازه را تعیین کند. راه‌حل: (1) به LazyColumn ارتفاع ثابت بدهید: Modifier.height(400.dp)، (2) داخل Column از Modifier.weight(1f) استفاده کنید، (3) LazyColumn را در ظرف قابل پیمایش عمودی قرار ندهید — از LazyColumn به عنوان عنصر ریشه با item {} برای header و footer استفاده کنید.

چگونه LazyColumn را هنگام تغییر داده به‌روزرسانی کنیم؟

LazyColumn به طور خودکار به تغییرات State واکنش نشان می‌دهد. اگر داده در mutableStateListOf یا mutableStateOf ذخیره شود، Compose فقط عناصر تغییر یافته را دوباره ترکیب می‌کند. برای لیست‌های ViewModel از collectAsState() با Flow استفاده کنید. هنگام به‌روزرسانی لیست با key، Compose به طور خودکار تغییرات (افزودن، حذف، جابجایی) را از طریق Modifier.animateItemPlacement() انیمیشن می‌کند.

LazyColumn vs LazyVerticalGrid — کدام را انتخاب کنیم؟

LazyColumn — برای لیست‌های تک ستونی (فید، چت، نظرات). LazyVerticalGrid — برای شبکه‌ها (گالری، کاتالوگ، آیکون‌ها). اگر عناصر باید در تلفن یک ستونه و در تبلت دو ستونه باشند — از LazyVerticalGrid با GridCells.Adaptive استفاده کنید. اگر شبکه مختلط (تعداد ستون متفاوت در یک بخش) نیاز دارید — از LazyColumn استفاده کنید و در داخل items، LazyRow افقی یا LazyVerticalGrid تودرتو قرار دهید.

خلاصه

  • LazyColumn — کامپوننت Jetpack Compose برای لیست‌های عمودی تنبل با API اعلامی.
  • items() API یک لیست و بلوک DSL برای هر عنصر می‌پذیرد؛ key برای عملکرد الزامی است.
  • LazyListState موقعیت پیمایش را مدیریت می‌کند، توابع suspend را برای پیمایش برنامه‌ریزی شده فراهم می‌کند.
  • contentType استخرهای ترکیب را برای لیست‌هایی با انواع مختلف عناصر جدا می‌کند.
  • LazyVerticalGrid / LazyRow — انواعی برای شبکه‌ها و لیست‌های افقی با همان items API.
  • LazyColumn به لطف SubcomposeLayout و بافر prefetch برای 10000+ عنصر 60 FPS تضمین می‌کند.
  • برای پروژه‌های جدید Compose از LazyColumn استفاده کنید؛ برای پروژه‌های View-based — RecyclerView.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید