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

कई ऐप्लिकेशन को आइटम के कलेक्शन दिखाने की ज़रूरत होती है. इस दस्तावेज़ में बताया गया है कि 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 कंपोज़ेबल का इस्तेमाल करके, ग्रिड में आइटम दिखाए जा सकते हैं. लेज़ी वर्टिकल ग्रिड, अपने आइटम को वर्टिकल तौर पर स्क्रोल किए जा सकने वाले कंटेनर में दिखाएगा. यह कंटेनर, कई कॉलम में फैला होगा. वहीं, लेज़ी हॉरिज़ॉन्टल ग्रिड, हॉरिज़ॉन्टल ऐक्सिस पर भी इसी तरह काम करेंगे.

ग्रिड में, सूचियों की तरह ही बेहतर API की सुविधाएं होती हैं. साथ ही, कॉन्टेंट के बारे में बताने के लिए, वे बहुत मिलते-जुलते डीएसएल - 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 की मदद से क्रम बदल सकते हैं. उदाहरण के लिए, अगर आपके आइटम में सेव की गई स्थिति है, तो सेटिंग बटन की मदद से, Compose को आइटम की स्थिति बदलने पर, इस स्थिति को आइटम के साथ ले जाने की अनुमति मिलेगी.

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

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

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

कुंजी Bundle के साथ काम करनी चाहिए, ताकि गतिविधि को फिर से बनाते समय, आइटम कंपोज़ेबल में मौजूद 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)
    ) {
        // ...
    }
}

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

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

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 जोड़ें

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

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

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

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

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