लिस्ट और ग्रिड

कई ऐप्लिकेशन को आइटम के कलेक्शन दिखाने की ज़रूरत होती है. इस दस्तावेज़ में बताया गया है कि Jetpack Compose में, इसे बेहतर तरीके से कैसे किया जा सकता है.

अगर आपको पता है कि इस्तेमाल के उदाहरण के लिए किसी स्क्रोलिंग की ज़रूरत नहीं है, तो दिशा के आधार पर आसान Column या Row का इस्तेमाल करें. साथ ही, एक सूची पर फिर से इस तरह हर आइटम का कॉन्टेंट दिखाएं:

@Composable
fun MessageList(messages: List<Message>) {
    Column {
        messages.forEach { message ->
            MessageRow(message)
        }
    }
}

verticalScroll() मॉडिफ़ायर का इस्तेमाल करके, Column को स्क्रोल किया जा सकता है.

लेज़ी लिस्ट

अगर आपको बहुत ज़्यादा आइटम (या किसी ऐसी सूची को दिखाना है जिसकी लंबाई का पता नहीं है), तो Column जैसे लेआउट का इस्तेमाल करने पर परफ़ॉर्मेंस से जुड़ी समस्याएं आ सकती हैं. इसकी वजह यह है कि सभी आइटम को कॉम्पोज़ और लेआउट किया जाएगा, भले ही वे दिख रहे हों या नहीं.

Compose, कॉम्पोनेंट का एक सेट उपलब्ध कराता है. यह सेट सिर्फ़ उन आइटम को कॉम्पोनेट में जोड़ता है और उनका लेआउट तय करता है जो कॉम्पोनेंट के व्यूपोर्ट में दिखते हैं. इन कॉम्पोनेंट में LazyColumn और LazyRow शामिल हैं.

जैसा कि नाम से पता चलता है, LazyColumn और LazyRow के बीच का अंतर यह है कि वे अपने आइटम किस ओर सेट करते हैं और किस ओर स्क्रोल करते हैं. LazyColumn इससे वर्टिकल स्क्रोलिंग सूची बनाई जाती है और LazyRow, हॉरिज़ॉन्टल रूप से स्क्रोल की जाने वाली सूची बनाती है.

लैज़ी कॉम्पोनेंट, Compose में मौजूद ज़्यादातर लेआउट से अलग होते हैं. ऐप्लिकेशन को सीधे कंपोज़ेबल बनाने की अनुमति देने के लिए, @Composable कॉन्टेंट ब्लॉक पैरामीटर को स्वीकार करने के बजाय, लेज़ी कॉम्पोनेंट एक LazyListScope.() ब्लॉक देते हैं. यह LazyListScope ब्लॉक, डीएसएल (डेटा स्टोरेज लैंग्वेज) की सुविधा देता है. इससे ऐप्लिकेशन, आइटम के कॉन्टेंट के बारे में जानकारी दे पाते हैं. इसके बाद, लेआउट और स्क्रोल पोज़िशन के हिसाब से, हर आइटम के कॉन्टेंट को जोड़ना, लेज़ी कॉम्पोनेंट की ज़िम्मेदारी होती है.

LazyListScope डीएसएल

LazyListScope का डीएसएल, लेआउट में आइटम के बारे में बताने के लिए कई फ़ंक्शन उपलब्ध कराता है. सबसे बुनियादी तौर पर, item() एक आइटम जोड़ता है और items(Int) कई आइटम जोड़ता है:

LazyColumn {
    // Add a single item
    item {
        Text(text = "First item")
    }

    // Add 5 items
    items(5) { index ->
        Text(text = "Item: $index")
    }

    // Add another single item
    item {
        Text(text = "Last item")
    }
}

एक्सटेंशन के कई फ़ंक्शन भी हैं, जिनकी मदद से आइटम के कलेक्शन जोड़े जा सकते हैं. जैसे, List. इन एक्सटेंशन की मदद से, हम ऊपर दिए गए अपने Column उदाहरण को आसानी से माइग्रेट कर सकते हैं:

/**
 * import androidx.compose.foundation.lazy.items
 */
LazyColumn {
    items(messages) { message ->
        MessageRow(message)
    }
}

items() एक्सटेंशन फ़ंक्शन का एक वैरिएंट भी है, जिसे itemsIndexed() कहा जाता है. यह इंडेक्स उपलब्ध कराता है. ज़्यादा जानकारी के लिए, कृपया LazyListScope का रेफ़रंस देखें.

लेज़ी ग्रिड

LazyVerticalGrid और LazyHorizontalGrid कंपोज़ेबल का इस्तेमाल करके, ग्रिड में आइटम दिखाए जा सकते हैं. लेज़ी वर्टिकल ग्रिड अपने आइटम को वर्टिकल तौर पर स्क्रोल किए जा सकने वाले कंटेनर में दिखाती है. यह कंटेनर कई कॉलम में दिखता है. वहीं लेज़ी हॉरिज़ॉन्टल ग्रिड, हॉरिज़ॉन्टल ऐक्सिस पर इसी तरह काम करती हैं.

ग्रिड में एपीआई की सुविधाएं, लिस्ट जैसी ही होती हैं. साथ ही, इनमें कॉन्टेंट की जानकारी देने के लिए, काफ़ी मिलते-जुलते DSL - LazyGridScope.() का इस्तेमाल किया जाता है.

फ़ोन का स्क्रीनशॉट, जिसमें फ़ोटो का ग्रिड दिख रहा है

LazyVerticalGrid में मौजूद columns पैरामीटर और LazyHorizontalGrid में मौजूद rows पैरामीटर से यह कंट्रोल होता है कि सेल को कॉलम या पंक्तियों में कैसे बनाया जाए. इस उदाहरण में दिए गए उदाहरण में, आइटम को ग्रिड में दिखाया गया है. इसके लिए, GridCells.Adaptive का इस्तेमाल करके, हर कॉलम की चौड़ाई कम से कम 128.dp पर सेट की गई है:

LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 128.dp)
) {
    items(photos) { photo ->
        PhotoItem(photo)
    }
}

LazyVerticalGrid की मदद से, आइटम की चौड़ाई तय की जा सकती है. इसके बाद, ग्रिड में ज़्यादा से ज़्यादा कॉलम फ़िट हो जाएंगे. कॉलम की संख्या का हिसाब लगाने के बाद, बाकी बची चौड़ाई को कॉलम के बीच बराबर बांटा जाता है. साइज़ तय करने का यह अडैप्टिव तरीका, अलग-अलग स्क्रीन साइज़ पर आइटम के सेट दिखाने के लिए खास तौर पर मददगार होता है.

अगर आपको इस्तेमाल किए जाने वाले कॉलम की सटीक संख्या पता है, तो ज़रूरी कॉलम की संख्या वाले GridCells.Fixed का उदाहरण दें.

अगर आपके डिज़ाइन में सिर्फ़ कुछ आइटम के लिए स्टैंडर्ड डाइमेंशन की ज़रूरत है, तो आइटम के लिए कस्टम कॉलम स्पैन देने के लिए, ग्रिड सपोर्ट का इस्तेमाल किया जा सकता है. LazyGridScope DSL item और items तरीकों के span पैरामीटर के साथ कॉलम की अवधि तय करें. maxLineSpan, स्पैन स्कोप की एक वैल्यू, खास तौर पर तब काम आती है, जब कस्टम साइज़ का इस्तेमाल किया जा रहा हो. ऐसा इसलिए होता है, क्योंकि कॉलम की संख्या तय नहीं होती है. इस उदाहरण में, लाइन का पूरा हिस्सा देने का तरीका बताया गया है:

LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 30.dp)
) {
    item(span = {
        // LazyGridItemSpanScope:
        // maxLineSpan
        GridItemSpan(maxLineSpan)
    }) {
        CategoryCard("Fruits")
    }
    // ...
}

लेज़ी स्टेजर्ड ग्रिड

LazyVerticalStaggeredGrid और LazyHorizontalStaggeredGrid, ऐसे कॉम्पोज़ेबल हैं जिनकी मदद से, आइटम का ऐसा ग्रिड बनाया जा सकता है जो धीरे-धीरे लोड होता है और जिसके आइटम अलग-अलग लेआउट में होते हैं. लेज़ी वर्टिकल स्टैगर्ड ग्रिड, अपने आइटम को वर्टिकल तौर पर स्क्रोल किए जा सकने वाले कंटेनर में दिखाता है. यह कंटेनर कई कॉलम में फैला होता है और अलग-अलग आइटम की ऊंचाई अलग-अलग हो सकती है. अलग-अलग चौड़ाई वाले आइटम के लिए, हॉरिज़ॉन्टल ऐक्सिस पर लेज़ी हॉरिज़ॉन्टल ग्रिड एक जैसा काम करते हैं.

यहां दिया गया स्निपेट, हर आइटम के लिए 200.dp चौड़ाई के साथ LazyVerticalStaggeredGrid का इस्तेमाल करने का एक बुनियादी उदाहरण है:

LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Adaptive(200.dp),
    verticalItemSpacing = 4.dp,
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    content = {
        items(randomSizedPhotos) { photo ->
            AsyncImage(
                model = photo,
                contentScale = ContentScale.Crop,
                contentDescription = null,
                modifier = Modifier
                    .fillMaxWidth()
                    .wrapContentHeight()
            )
        }
    },
    modifier = Modifier.fillMaxSize()
)

पहली इमेज. धीरे-धीरे रैंक में बढ़ने वाले वर्टिकल ग्रिड का उदाहरण

कॉलम की तय संख्या सेट करने के लिए, StaggeredGridCells.Adaptive के बजाय StaggeredGridCells.Fixed(columns) का इस्तेमाल किया जा सकता है. यह मौजूदा चौड़ाई को कॉलम की संख्या (या हॉरिज़ॉन्टल ग्रिड के लिए लाइनें) से भाग देता है और इससे हर आइटम उस चौड़ाई (या हॉरिज़ॉन्टल ग्रिड के लिए ऊंचाई) तक पहुंच जाता है:

LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Fixed(3),
    verticalItemSpacing = 4.dp,
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    content = {
        items(randomSizedPhotos) { photo ->
            AsyncImage(
                model = photo,
                contentScale = ContentScale.Crop,
                contentDescription = null,
                modifier = Modifier
                    .fillMaxWidth()
                    .wrapContentHeight()
            )
        }
    },
    modifier = Modifier.fillMaxSize()
)

Compose में इमेज की लेज़ी स्टैग की गई ग्रिड
दूसरी इमेज. तय किए गए कॉलम के साथ लेज़ी स्टेज किए गए वर्टिकल ग्रिड का उदाहरण

कॉन्टेंट पैडिंग

कभी-कभी आपको कॉन्टेंट के किनारों के आस-पास पैडिंग (जगह) जोड़ने की ज़रूरत पड़ सकती है. लैज़ी कॉम्पोनेंट की मदद से, contentPadding पैरामीटर में कुछ PaddingValues पास किए जा सकते हैं, ताकि यह काम कर सके:

LazyColumn(
    contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
    // ...
}

इस उदाहरण में, हम हॉरिज़ॉन्टल किनारों (बाईं और दाईं ओर) पर 16.dp पैडिंग जोड़ते हैं. इसके बाद, कॉन्टेंट के ऊपर और नीचे 8.dp पैडिंग जोड़ते हैं.

कृपया ध्यान दें कि यह पैडिंग कॉन्टेंट पर लागू होती है, न कि LazyColumn पर. ऊपर दिए गए उदाहरण में, पहले आइटम के ऊपर 8.dp padding जोड़ा जाएगा, आखिरी आइटम के नीचे 8.dp padding जोड़ा जाएगा, और सभी आइटम के बाईं और दाईं ओर 16.dp padding जोड़ा जाएगा.

कॉन्टेंट के बीच स्पेस

आइटम के बीच में स्पेस जोड़ने के लिए, Arrangement.spacedBy() का इस्तेमाल किया जा सकता है. नीचे दिए गए उदाहरण में हर आइटम के बीच 4.dp की जगह जोड़ी गई है:

LazyColumn(
    verticalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}

इसी तरह, LazyRow के लिए:

LazyRow(
    horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}

हालांकि, ग्रिड में वर्टिकल और हॉरिज़ॉन्टल, दोनों तरह के क्रम में आइटम व्यवस्थित किए जा सकते हैं:

LazyVerticalGrid(
    columns = GridCells.Fixed(2),
    verticalArrangement = Arrangement.spacedBy(16.dp),
    horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
    items(photos) { item ->
        PhotoItem(item)
    }
}

आइटम की कुंजियां

डिफ़ॉल्ट रूप से, हर आइटम की स्थिति को सूची या ग्रिड में आइटम की जगह से मैच किया जाता है. हालांकि, अगर डेटा सेट में बदलाव होता है, तो इससे समस्याएं आ सकती हैं. ऐसा इसलिए, क्योंकि जिन आइटम की जगह बदल जाती है वे याद की गई स्थिति को खो देते हैं. अगर LazyColumn में LazyRow के क्रम की कल्पना की जाती है और अगर पंक्ति में आइटम की स्थिति बदल जाती है, तो उपयोगकर्ता पंक्ति में स्क्रोल करने की जगह खो देगा.

इससे बचने के लिए, हर आइटम के लिए एक स्थायी और यूनीक कुंजी दी जा सकती है. साथ ही, key पैरामीटर के लिए ब्लॉक भी दिया जा सकता है. स्थिर कुंजी देने से, डेटा सेट में होने वाले सभी बदलावों के लिए, आइटम की स्थिति एक जैसी बनी रहती है:

LazyColumn {
    items(
        items = messages,
        key = { message ->
            // Return a stable + unique key for the item
            message.id
        }
    ) { message ->
        MessageRow(message)
    }
}

कुंजियां उपलब्ध कराने से, आप Compose की मदद से क्रम बदल सकते हैं. उदाहरण के लिए, अगर आपके आइटम में याद रखी गई स्थिति शामिल है, तो कुंजियों को सेट करने से इसकी स्थिति बदलने पर, कंपोज़ की सुविधा को आइटम के साथ इस स्थिति को ले जाने की अनुमति मिल जाती है.

LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = remember {
            Random.nextInt()
        }
    }
}

हालांकि, आइटम की कुंजियों के तौर पर कुछ खास तरह के आइटम का इस्तेमाल किया जा सकता है. बटन के टाइप के साथ Bundle काम करना चाहिए. यह Android का एक तरीका है, जिसकी मदद से ऐक्टिविटी को फिर से शुरू करने पर, उसकी स्थितियों को बनाए रखा जाता है. Bundle, प्रिमिटिव, ईनम या पार्सल जैसे टाइप के साथ काम करता है.

LazyColumn {
    items(books, key = {
        // primitives, enums, Parcelable, etc.
    }) {
        // ...
    }
}

यह ज़रूरी है कि बटन, Bundle के साथ काम करता हो, ताकि ऐक्टिविटी को फिर से बनाने पर, आइटम कॉम्पोज़ेबल में मौजूद rememberSaveable को वापस लाया जा सके. इसके अलावा, इस आइटम से स्क्रोल करके वापस आने पर भी, rememberSaveable को वापस लाया जा सकता है.

LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = rememberSaveable {
            Random.nextInt()
        }
    }
}

आइटम पर आधारित ऐनिमेशन

अगर आपने RecyclerView विजेट का इस्तेमाल किया है, तो आपको पता होगा कि यह आइटम में होने वाले बदलावों को ऐनिमेशन के साथ दिखाता है. आइटम के क्रम में बदलाव करने के लिए, लेज़ी लेआउट में भी वही फ़ंक्शन मिलते हैं जो अन्य लेआउट में मिलते हैं. यह एपीआई इस्तेमाल करना आसान है - आपको आइटम के कॉन्टेंट में सिर्फ़ animateItem मॉडिफ़ायर को सेट करना होगा:

LazyColumn {
    // It is important to provide a key to each item to ensure animateItem() works as expected.
    items(books, key = { it.id }) {
        Row(Modifier.animateItem()) {
            // ...
        }
    }
}

ज़रूरत पड़ने पर, कस्टम ऐनिमेशन स्पेसिफ़िकेशन भी दिए जा सकते हैं:

LazyColumn {
    items(books, key = { it.id }) {
        Row(
            Modifier.animateItem(
                fadeInSpec = tween(durationMillis = 250),
                fadeOutSpec = tween(durationMillis = 100),
                placementSpec = spring(stiffness = Spring.StiffnessLow, dampingRatio = Spring.DampingRatioMediumBouncy)
            )
        ) {
            // ...
        }
    }
}

पक्का करें कि आपने अपने आइटम के लिए कुंजियां दी हों, ताकि मूव किए गए एलिमेंट की नई जगह ढूंढी जा सके.

स्टिक हेडर (प्रयोग के तौर पर उपलब्ध)

ग्रुप किए गए डेटा की सूचियां दिखाते समय, 'स्टिकी हेडर' पैटर्न मददगार होता है. नीचे 'संपर्क सूची' का एक उदाहरण दिया गया है, जिसे हर संपर्क के नाम के पहले अक्षर के हिसाब से ग्रुप किया गया है:

फ़ोन का वीडियो, जिसमें संपर्क सूची में ऊपर और नीचे की ओर स्क्रोल करते हुए दिखाया गया है

LazyColumn के साथ स्टिकी हेडर हासिल करने के लिए, एक्सपेरिमेंट के तौर पर शुरू किए गए stickyHeader() फ़ंक्शन का इस्तेमाल किया जा सकता है. इसके लिए, यह हेडर कॉन्टेंट उपलब्ध कराया जा सकता है:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListWithHeader(items: List<Item>) {
    LazyColumn {
        stickyHeader {
            Header()
        }

        items(items) { item ->
            ItemRow(item)
        }
    }
}

ऊपर दिए गए 'संपर्कों की सूची' के उदाहरण की तरह, एक से ज़्यादा हेडर वाली सूची बनाने के लिए, ये काम किए जा सकते हैं:

// This ideally would be done in the ViewModel
val grouped = contacts.groupBy { it.firstName[0] }

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ContactsList(grouped: Map<Char, List<Contact>>) {
    LazyColumn {
        grouped.forEach { (initial, contactsForInitial) ->
            stickyHeader {
                CharacterHeader(initial)
            }

            items(contactsForInitial) { contact ->
                ContactListItem(contact)
            }
        }
    }
}

स्क्रोल करने की जगह बदलने के लिए प्रतिक्रिया दिख रही है

कई ऐप्लिकेशन को स्क्रोल की पोज़िशन और आइटम के लेआउट में होने वाले बदलावों पर प्रतिक्रिया देने और उन्हें सुनने की ज़रूरत होती है. लेज़ी कॉम्पोनेंट, इस्तेमाल के इस उदाहरण को LazyListState को उठाकर काम करते हैं:

@Composable
fun MessageList(messages: List<Message>) {
    // Remember our own LazyListState
    val listState = rememberLazyListState()

    // Provide it to LazyColumn
    LazyColumn(state = listState) {
        // ...
    }
}

आसान मामलों में, ऐप्लिकेशन को आम तौर पर सिर्फ़ पहले दिखने वाले आइटम के बारे में जानकारी की ज़रूरत होती है. इसके लिए LazyListState से firstVisibleItemIndex और firstVisibleItemScrollOffset प्रॉपर्टी की जानकारी मिलती है.

अगर हम उपयोगकर्ता के पहले आइटम को स्क्रोल करने के आधार पर, बटन को दिखाने और छिपाने के उदाहरण का इस्तेमाल करते हैं, तो:

@Composable
fun MessageList(messages: List<Message>) {
    Box {
        val listState = rememberLazyListState()

        LazyColumn(state = listState) {
            // ...
        }

        // Show the button if the first visible item is past
        // the first item. We use a remembered derived state to
        // minimize unnecessary compositions
        val showButton by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex > 0
            }
        }

        AnimatedVisibility(visible = showButton) {
            ScrollToTopButton()
        }
    }
}

सीधे कंपोज़िशन में स्टेटस पढ़ना तब फ़ायदेमंद होता है, जब आपको अन्य यूज़र इंटरफ़ेस (यूआई) कॉम्पोज़ेबल अपडेट करने हों. हालांकि, ऐसे भी मामले होते हैं जहां इवेंट को उसी कंपोज़िशन में मैनेज करने की ज़रूरत नहीं होती. इसका एक सामान्य उदाहरण यह है कि जब उपयोगकर्ता किसी खास पॉइंट तक स्क्रोल कर लेता है, तो Analytics इवेंट भेजना. इसे बेहतर तरीके से मैनेज करने के लिए, हम snapshotFlow() का इस्तेमाल कर सकते हैं:

val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}

LazyListState, layoutInfo प्रॉपर्टी के ज़रिए, स्क्रीन पर दिख रहे सभी आइटम और उनके बॉउंड की जानकारी भी देता है. ज़्यादा जानकारी के लिए, LazyListLayoutInfo क्लास देखें.

स्क्रोल की पोज़िशन कंट्रोल करना

स्क्रोल पोज़िशन पर प्रतिक्रिया देने के साथ-साथ, ऐप्लिकेशन के लिए भी यह मददगार है कि वे स्क्रोल की पोज़िशन को भी कंट्रोल कर पाएँ. LazyListState इसके लिए, scrollToItem() फ़ंक्शन का इस्तेमाल किया जाता है. यह फ़ंक्शन, स्क्रॉल की पोज़िशन को 'तुरंत' स्नैप करता है. साथ ही, animateScrollToItem() ऐनिमेशन (इसे स्मूद स्क्रॉल भी कहा जाता है) का इस्तेमाल करके स्क्रॉल करता है:

@Composable
fun MessageList(messages: List<Message>) {
    val listState = rememberLazyListState()
    // Remember a CoroutineScope to be able to launch
    val coroutineScope = rememberCoroutineScope()

    LazyColumn(state = listState) {
        // ...
    }

    ScrollToTopButton(
        onClick = {
            coroutineScope.launch {
                // Animate scroll to the first item
                listState.animateScrollToItem(index = 0)
            }
        }
    )
}

बड़े डेटा-सेट (पेजिंग)

पेजिंग लाइब्रेरी की मदद से ऐप्लिकेशन, आइटम की बड़ी सूचियों के साथ काम कर सकते हैं. इससे सूची के छोटे-छोटे हिस्सों को ज़रूरत के मुताबिक लोड किया जा सकता है और दिखाया जा सकता है. पेज 3.0 और इसके बाद वाले वर्शन में, androidx.paging:paging-compose लाइब्रेरी की मदद से लिखने की सुविधा मिलती है.

पेज पर मौजूद कॉन्टेंट की सूची दिखाने के लिए, हम collectAsLazyPagingItems() एक्सटेंशन फ़ंक्शन का इस्तेमाल करके, LazyColumn में लौटाए गए LazyPagingItems फ़ंक्शन को items() पर पास कर सकते हैं. व्यू में पेजिंग की सुविधा की तरह ही, डेटा लोड होने के दौरान प्लेसहोल्डर दिखाया जा सकता है. इसके लिए, यह जांच करें कि item, null है या नहीं:

@Composable
fun MessageList(pager: Pager<Int, Message>) {
    val lazyPagingItems = pager.flow.collectAsLazyPagingItems()

    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it.id }
        ) { index ->
            val message = lazyPagingItems[index]
            if (message != null) {
                MessageRow(message)
            } else {
                MessagePlaceholder()
            }
        }
    }
}

लेज़ी लेआउट का इस्तेमाल करने से जुड़ी सलाह

यहां कुछ सुझाव दिए गए हैं. इनकी मदद से, यह पक्का किया जा सकता है कि आपके लेज़ी लेआउट सही तरीके से काम करें.

0 पिक्सल वाले आइटम का इस्तेमाल करने से बचें

ऐसा उन स्थितियों में हो सकता है जहां, उदाहरण के लिए, आपकी सूची के आइटम को बाद में भरने के लिए, आपको इमेज जैसे कुछ डेटा को एसिंक्रोनस रूप से वापस पाने की उम्मीद होती है. ऐसा होने पर, लेज़ी लेआउट अपने सभी आइटम को पहले मेज़रमेंट में कॉम्पोज़ कर देगा, क्योंकि उनकी ऊंचाई 0 पिक्सल है और वे सभी आइटम व्यूपोर्ट में फ़िट हो सकते हैं. आइटम लोड होने और उनकी ऊंचाई बढ़ने के बाद, लेज़ी लेआउट उन सभी आइटम को हटा देंगे जिन्हें पहली बार ज़रूरत से ज़्यादा कंपोज किया गया है. ऐसा इसलिए, क्योंकि वे व्यूपोर्ट में फ़िट नहीं हो सकते. इससे बचने के लिए, आपको अपने आइटम के लिए डिफ़ॉल्ट साइज़ सेट करना चाहिए, ताकि लेज़ी लेआउट यह सही तरीके से कैलकुलेट कर सके कि व्यूपोर्ट में कितने आइटम फ़िट हो सकते हैं:

@Composable
fun Item(imageUrl: String) {
    AsyncImage(
        model = rememberAsyncImagePainter(model = imageUrl),
        modifier = Modifier.size(30.dp),
        contentDescription = null
        // ...
    )
}

डेटा को एसिंक्रोनस रूप से लोड होने के बाद, जब आपको आइटम का अनुमानित साइज़ पता हो, तो यह पक्का करना सबसे सही तरीका है कि लोड होने से पहले और बाद में आइटम का साइज़, पहले जैसा ही रहे. उदाहरण के लिए, कुछ प्लेसहोल्डर जोड़ना. इससे स्क्रोल की सही पोज़िशन बनाए रखने में मदद मिलेगी.

एक ही दिशा में स्क्रोल किए जा सकने वाले कॉम्पोनेंट को नेस्ट करने से बचें

यह सिर्फ़ उन मामलों पर लागू होता है जब स्क्रोल किए जा सकने वाले बच्चों को, पहले से तय किए गए साइज़ के बिना, एक ही दिशा में स्क्रोल किए जा सकने वाले पैरंट के अंदर नेस्ट किया जाता है. उदाहरण के लिए, वर्टिकल स्क्रोल करने लायक Column पैरंट में एक तय ऊंचाई के बिना, LazyColumn चाइल्ड को नेस्ट करने की कोशिश करना:

// throws IllegalStateException
Column(
    modifier = Modifier.verticalScroll(state)
) {
    LazyColumn {
        // ...
    }
}

इसके बजाय, एक ही नतीजा पाने के लिए, अपने सभी कॉम्पोज़ेबल को एक पैरंट LazyColumn में रैप करें. साथ ही, अलग-अलग तरह के कॉन्टेंट को पास करने के लिए, इसके डीएसएल का इस्तेमाल करें. इसकी मदद से, एक आइटम के साथ-साथ, सूची में मौजूद कई आइटम को एक ही जगह से निकाला जा सकता है:

LazyColumn {
    item {
        Header()
    }
    items(data) { item ->
        PhotoItem(item)
    }
    item {
        Footer()
    }
}

ध्यान रखें कि अलग-अलग दिशा वाले लेआउट को नेस्ट करने की अनुमति है. उदाहरण के लिए, स्क्रोल किए जा सकने वाले पैरंट Row और चाइल्ड LazyColumn:

Row(
    modifier = Modifier.horizontalScroll(scrollState)
) {
    LazyColumn {
        // ...
    }
}

साथ ही, ऐसे मामले जहां आप अब भी एक ही दिशा वाले लेआउट का इस्तेमाल करते हैं, लेकिन नेस्ट किए गए बच्चों के लिए एक तय साइज़ भी सेट करते हैं:

Column(
    modifier = Modifier.verticalScroll(scrollState)
) {
    LazyColumn(
        modifier = Modifier.height(200.dp)
    ) {
        // ...
    }
}

एक आइटम में एक से ज़्यादा एलिमेंट डालने से बचें

इस उदाहरण में, दूसरा आइटम lambda एक ब्लॉक में दो आइटम का उत्सर्जन करता है:

LazyVerticalGrid(
    columns = GridCells.Adaptive(100.dp)
) {
    item { Item(0) }
    item {
        Item(1)
        Item(2)
    }
    item { Item(3) }
    // ...
}

लेज़ी लेआउट इसे उम्मीद के मुताबिक हैंडल करेंगे - वे एलिमेंट को एक के बाद एक क्रम में दिखाएंगे, जैसे कि वे अलग-अलग आइटम हों. हालांकि, ऐसा करने में कुछ समस्याएं आती हैं.

जब किसी एक आइटम के हिस्से के रूप में कई एलिमेंट उत्सर्जित किए जाते हैं, तो उन्हें एक इकाई के तौर पर हैंडल किया जाता है. इसका मतलब है कि उन्हें अलग-अलग नहीं बनाया जा सकता. अगर स्क्रीन पर एक एलिमेंट दिखता है, तो उस आइटम से जुड़े सभी एलिमेंट को कंपोज और मेज़र करना होगा. अगर इसका बहुत ज़्यादा इस्तेमाल किया जाए, तो इससे परफ़ॉर्मेंस पर बुरा असर पड़ सकता है. सभी एलिमेंट को एक आइटम में डालने पर, लैज़ी लेआउट का इस्तेमाल करने का मकसद पूरी तरह से पूरा नहीं होता. परफ़ॉर्मेंस से जुड़ी संभावित समस्याओं के अलावा, एक आइटम में ज़्यादा एलिमेंट जोड़ने से scrollToItem() और animateScrollToItem() पर भी असर पड़ेगा.

हालांकि, एक आइटम में कई एलिमेंट डालने के लिए, इस्तेमाल के मान्य उदाहरण हैं. जैसे, किसी सूची में डिवाइडर होना. आपको डिवाइडर से स्क्रोलिंग इंडेक्स में बदलाव नहीं कराना है, क्योंकि उन्हें अलग एलिमेंट नहीं माना जाना चाहिए. साथ ही, डिवाइडर छोटे होने की वजह से परफ़ॉर्मेंस पर भी कोई असर नहीं पड़ेगा. आइटम के दिखने से पहले, डिवाइडर दिखना चाहिए, ताकि वह पिछले आइटम का हिस्सा बन सके:

LazyVerticalGrid(
    columns = GridCells.Adaptive(100.dp)
) {
    item { Item(0) }
    item {
        Item(1)
        Divider()
    }
    item { Item(2) }
    // ...
}

कस्टम क्रम का इस्तेमाल करें

आम तौर पर, लेज़ी लिस्ट में कई आइटम होते हैं और वे स्क्रोलिंग कंटेनर के साइज़ से ज़्यादा जगह लेते हैं. हालांकि, जब आपकी सूची में कुछ आइटम होते हैं, तो आपके डिज़ाइन में ज़्यादा खास ज़रूरतें हो सकती हैं. जैसे, व्यूपोर्ट में इन आइटम को कैसे पोज़िशन किया जाए.

इसके लिए, कस्टम वर्टिकल Arrangement का इस्तेमाल करें और उसे LazyColumn को पास करें. नीचे दिए गए उदाहरण में, TopWithFooter ऑब्जेक्ट को सिर्फ़ arrange तरीके को लागू करने की ज़रूरत है. सबसे पहले, यह आइटम को एक के बाद एक दिखाएगा. दूसरी बात, अगर इस्तेमाल की गई कुल ऊंचाई, व्यूपोर्ट की ऊंचाई से कम है, तो फ़ुटर को सबसे नीचे रखा जाएगा:

object TopWithFooter : Arrangement.Vertical {
    override fun Density.arrange(
        totalSize: Int,
        sizes: IntArray,
        outPositions: IntArray
    ) {
        var y = 0
        sizes.forEachIndexed { index, size ->
            outPositions[index] = y
            y += size
        }
        if (y < totalSize) {
            val lastIndex = outPositions.lastIndex
            outPositions[lastIndex] = totalSize - sizes.last()
        }
    }
}

contentType जोड़ें

Compose 1.2 से, अपने लेज़ी लेआउट की परफ़ॉर्मेंस को बेहतर बनाने के लिए, अपनी सूचियों या ग्रिड में contentType जोड़ें. इसकी मदद से, लेआउट के हर आइटम के लिए कॉन्टेंट टाइप तय किया जा सकता है. ऐसा तब किया जाता है, जब कई तरह के आइटम वाली सूची या ग्रिड बनाई जा रही हो:

LazyColumn {
    items(elements, contentType = { it.type }) {
        // ...
    }
}

contentType का इस्तेमाल करने पर, कंपोज़िशन सिर्फ़ एक ही तरह के आइटम के बीच कंपोज़िशन का फिर से इस्तेमाल कर सकती है. मिलते-जुलते स्ट्रक्चर वाले आइटम लिखने पर, फिर से इस्तेमाल करना ज़्यादा असरदार होता है. कॉन्टेंट टाइप देने से यह पक्का होता है कि Compose, टाइप B के किसी पूरी तरह से अलग आइटम के ऊपर, टाइप A का कोई आइटम लिखने की कोशिश न करे. इससे कंपोज़िशन के दोबारा इस्तेमाल से होने वाले फ़ायदों और लेज़ी लेआउट की परफ़ॉर्मेंस को बेहतर बनाने में मदद मिलती है.

परफ़ॉर्मेंस को मेज़र करना

रिलीज़ मोड में और R8 ऑप्टिमाइज़ेशन की सुविधा चालू होने पर ही, लेज़ी लेआउट की परफ़ॉर्मेंस को सही तरीके से मेज़र किया जा सकता है. डीबग बिल्ड पर, लेज़ी लेआउट स्क्रोलिंग धीमी हो सकती है. इस बारे में ज़्यादा जानकारी के लिए, कॉम्पोज़ की परफ़ॉर्मेंस लेख पढ़ें.