LazyColumn — ما هو، القوائم الفعالة في Compose

المؤلف: IT Sectr نُشر: 2026-02-24 وقت القراءة: 10 دق

LazyColumn — مكون Jetpack Compose لعرض القوائم القابلة للتمرير مع التحميل البطيء. على عكس RecyclerView، لا ينشئ LazyColumn ViewHolders ولا يستخدم XML — يتم وصف جميع العناصر بشكل تصريحي من خلال دوال composable. نعرض ما هو LazyColumn، وكيف يعمل LazyListState وواجهة items API، وكيفية تحقيق أداء 60 إطارًا في الثانية. في 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 تعرض قائمة رأسية من العناصر مع التحميل البطيء: يتم إنشاء وتركيب العناصر المرئية على الشاشة أو القريبة منها فقط (منطقة التحميل المسبق). ظهر LazyColumn في Compose 1.0 (2021) إلى جانب LazyRow (قائمة أفقية) وLazyVerticalGrid (شبكة).

التحميل البطيء يعمل من خلال آلية SubcomposeLayout: يقيس LazyColumn المساحة المتاحة، ويطلب النطاق المرئي من LayoutInfo، ويقوم بتركيب العناصر في هذا النطاق فقط. العناصر التي تخرج عن الشاشة تغادر التركيب (باستثناء مخزن التحميل المسبق). وفقًا لـ Google (أداء Android، 2026)، يحافظ LazyColumn على 60 إطارًا في الثانية عند التمرير خلال قائمة من 10000+ عنصر على أجهزة الفئة المتوسطة.

الفرق مع Column + ScrollState

Column مع التمرير الرأسي هو ترتيب عادي لجميع العناصر واحدًا تحت الآخر مع تطبيق معدِّل verticalScroll. Column ليست بطيئة: جميع العناصر التابعة تُركب فورًا حتى لو لم تكن مرئية. لقائمة من 200+ عنصر، تسبب Column تأخيرًا كبيرًا عند بدء التشغيل. يقوم LazyColumn بتركيب العناصر المرئية فقط + المخزن المؤقت (شاشة واحدة للأمام/للخلف افتراضيًا)، مما يجعله لا غنى عنه للقوائم الطويلة.

items API: ربط البيانات بالقائمة

items API — مجموعة من دوال الامتداد لـ 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 {} ضد items(): استخدم item {} للعناصر المفردة الموجودة دائمًا في القائمة (رأس، تذييل، فواصل). استخدم items() لقائمة بيانات ديناميكية. تقبل items() Iterable أو Int وكتلة DSL يتم استدعاؤها لكل عنصر. ادمج item وitems في LazyColumn واحد: ترتيب الاستدعاءات يحدد ترتيب العرض.

LazyListState: إدارة موضع التمرير

LazyListState — كائن يخزن حالة القائمة: موضع التمرير الحالي (firstVisibleItemIndex, firstVisibleItemScrollOffset)، العناصر المرئية (layoutInfo)، وطرق التمرير البرمجي (scrollToItem, animateScrollToItem). يتم إنشاء LazyListState عبر rememberLazyListState() ويتم تمريره إلى LazyColumn عبر معامل state.

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 في listState: scrollToItem(index, scrollOffset) — نقل فوري (بدون رسوم متحركة)، animateScrollToItem(index) — مع رسوم متحركة. للتمرير من coroutine، استخدم LaunchedEffect أو coroutineScope.launch. مهم: لا تستدع دوال suspend من التركيب (داخل @Composable) — استخدم لامبدا (onClick, LaunchedEffect).

LazyColumn مقابل RecyclerView: مقارنة النهج

LazyColumn وRecyclerView يحلان نفس المشكلة — العرض الفعال للقوائم الطويلة. الفرق في البنية: RecyclerView يستخدم ViewHolder وAdapter (عرض Android الكلاسيكي)، LazyColumn يستخدم دوال composable تصريحية بدون XML أو ViewHolder. يعتمد الاختيار على حزمة التقنيات للمشروع: Compose مقابل واجهة المستخدم القائمة على View.

المعامل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 + ViewHolders متعددة
التمرير إلى موضعlistState.animateScrollToItem()layoutManager.scrollToPosition()
التحميل المسبقداخل العملية (مخزن SubcomposeLayout)RecycledViewPool + GapWorker
الشبكة/التصفحcollectAsLazyPagingItems() + Paging 3PagingDataAdapter + Paging 3

توصية Google (Android Developers, 2026): للمشاريع الجديدة على Jetpack Compose، استخدم LazyColumn. للمشاريع القائمة على View، استخدم RecyclerView. يتطلب LazyColumn Compose، الذي يضيف حوالي 3–5 ميجابايت إلى APK — ضع ذلك في الاعتبار عند دعم الأجهزة القديمة. إذا كان المشروع يستخدم بالفعل نظام View، فلا يمكن لـ LazyColumn التعايش في شاشة واحدة مع تخطيط XML بدون AndroidView — في مثل هذه الحالات، من الأسهل البقاء مع RecyclerView.

LazyVerticalGrid وLazyHorizontalGrid: الشبكات

LazyVerticalGrid — إصدار من LazyColumn للشبكات بعدد ثابت من الأعمدة. LazyHorizontalGrid — شبكة أفقية بعدد ثابت من الصفوف. يستخدم كلا المكونين نفس مبادئ التحميل البطيء ونفس واجهة 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 تضع عمودين، على جهاز لوحي 600 dp — 4 أعمدة. GridCells.Fixed(2) يعرض دائمًا عمودين بالضبط. يُفضل Adaptive للتكيف مع الشاشات المتعددة — يضبط تلقائيًا عرض الشاشة بدون استعلامات وسائط.

أداء LazyColumn: key وcontentType وprefetch

أداء LazyColumn يعتمد على ثلاثة عوامل: استقرار المفتاح (key)، نوع المحتوى (contentType)، ومخزن التحميل المسبق (beyondBounds). افتراضيًا، يخزن LazyColumn شاشة واحدة للأمام وشاشة واحدة للخلف. توصي Google (Android Developers, 2026) بتكوين هذه المعاملات للقوائم بأنواع عناصر مختلفة أو تركيب معقد.

المعاملالوصفالتوصية
keyمعرف ثابت للعنصرحدد دائمًا key = { it.id }. بدون key، يستخدم Compose الفهرس، وإعادة ترتيب العناصر يكسر الرسوم المتحركة.
contentTypeنوع المحتوى لفصل المجموعاتحدد للقوائم بأنواع عناصر مختلفة (نص + صورة + إعلان). سيعيد Compose استخدام التركيب فقط داخل نفس contentType.
beyondBoundsعدد شاشات التحميل المسبقزده إلى 2–3 للقوائم ذات الصور الثقيلة. الافتراضي هو شاشة واحدة.
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  // تحميل مسبق لشاشتين
    ) {
        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 بمفتاح حسب البيانات. استخدم Modifier.drawWithContent بدلاً من Image للرسومات البسيطة.

كيفية إضافة فاصل بين عناصر 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) استخدم Modifier.weight(1f) داخل Column، (3) لا تتداخل LazyColumn داخل حاوية قابلة للتمرير رأسيًا — استخدم LazyColumn كعنصر جذر مع item {} للرأس والتذييل.

كيفية تحديث LazyColumn عند تغيير البيانات؟

LazyColumn يتفاعل تلقائيًا مع تغييرات State. إذا كانت البيانات مخزنة في mutableStateListOf أو mutableStateOf، يعيد Compose تركيب العناصر المتغيرة فقط. للقوائم من ViewModel، استخدم collectAsState() مع Flow. عند تحديث القائمة باستخدام key، يقوم Compose تلقائيًا بتحريك التغييرات (إضافة، إزالة، إعادة ترتيب) عبر Modifier.animateItemPlacement().

LazyColumn مقابل LazyVerticalGrid — أيهما تختار؟

LazyColumn — للقوائم بعمود واحد (تغذية، دردشة، تعليقات). LazyVerticalGrid — للشبكات (معرض، كتالوج، أيقونات). إذا كان يجب أن تكون العناصر في عمود واحد على الهاتف وعمودين على الجهاز اللوحي — استخدم LazyVerticalGrid مع GridCells.Adaptive. إذا كانت هناك حاجة لشبكة مختلطة (عدد أعمدة مختلف في قسم واحد) — استخدم LazyColumn وأدخل LazyRow أفقي أو LazyVerticalGrid متداخلة داخل items.

الخلاصة

  • LazyColumn — مكون Jetpack Compose للقوائم الرأسية البطيئة مع واجهة API تصريحية.
  • items() API يقبل قائمة وكتلة DSL لكل عنصر؛ key إلزامي للأداء.
  • LazyListState يدير موضع التمرير، ويوفر دوال suspend للتمرير البرمجي.
  • contentType يفصل مجموعات التركيب للقوائم بأنواع عناصر مختلفة.
  • LazyVerticalGrid / LazyRow — متغيرات للشبكات والقوائم الأفقية بنفس واجهة items API.
  • LazyColumn يحافظ على 60 إطارًا في الثانية لـ 10000+ عنصر بفضل SubcomposeLayout ومخزن التحميل المسبق.
  • للمشاريع الجديدة في Compose استخدم LazyColumn؛ للمشاريع القائمة على View — RecyclerView.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا