कई ऐप्लिकेशन को आइटम के कलेक्शन दिखाने होते हैं. इस दस्तावेज़ में बताया गया है कि 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
कॉन्टेंट ब्लॉक पैरामीटर स्वीकार करने के बजाय, Lazy कॉम्पोनेंट एक LazyListScope.()
ब्लॉक उपलब्ध कराते हैं. इससे ऐप्लिकेशन, कंपोज़ेबल को सीधे तौर पर दिखा पाते हैं. इस LazyListScope
ब्लॉक में एक डीएसएल उपलब्ध है. इसकी मदद से ऐप्लिकेशन, आइटम के कॉन्टेंट के बारे में बता सकते हैं. इसके बाद, Lazy कॉम्पोनेंट की यह ज़िम्मेदारी होती है कि वह लेआउट और स्क्रोल पोज़िशन की ज़रूरत के हिसाब से, हर आइटम का कॉन्टेंट जोड़े.
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
कंपोज़ेबल, आइटम को ग्रिड में दिखाने की सुविधा देते हैं. लेज़ी वर्टिकल ग्रिड, अपने आइटम को वर्टिकल तौर पर स्क्रोल किए जा सकने वाले कंटेनर में दिखाएगा. यह कंटेनर, कई कॉलम में फैला होगा. वहीं, लेज़ी हॉरिज़ॉन्टल ग्रिड में हॉरिज़ॉन्टल ऐक्सिस पर यही व्यवहार दिखेगा.
ग्रिड में, लिस्ट की तरह ही एपीआई की बेहतर सुविधाएं होती हैं. साथ ही, ये कॉन्टेंट के बारे में बताने के लिए, एक जैसे डीएसएल का इस्तेमाल करते हैं -
LazyGridScope.()
.
LazyVerticalGrid
में मौजूद columns
पैरामीटर और LazyHorizontalGrid
में मौजूद rows
पैरामीटर से यह कंट्रोल किया जाता है कि सेल को कॉलम या लाइन में कैसे बनाया जाए. यहां दिए गए उदाहरण में, आइटम को ग्रिड में दिखाया गया है. इसमें GridCells.Adaptive
का इस्तेमाल करके, हर कॉलम को कम से कम 128.dp
चौड़ा सेट किया गया है:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 128.dp) ) { items(photos) { photo -> PhotoItem(photo) } }
LazyVerticalGrid
की मदद से, आइटम की चौड़ाई तय की जा सकती है. इसके बाद, ग्रिड में ज़्यादा से ज़्यादा कॉलम फ़िट हो जाएंगे. कॉलम की संख्या तय होने के बाद, बची हुई चौड़ाई को सभी कॉलम में बराबर बांट दिया जाता है.
यह अडैप्टिव साइज़िंग, खास तौर पर अलग-अलग साइज़ की स्क्रीन पर आइटम के सेट दिखाने के लिए फ़ायदेमंद होती है.
अगर आपको इस्तेमाल किए जाने वाले कॉलम की सही संख्या पता है, तो इसके बजाय, GridCells.Fixed
का एक इंस्टेंस दिया जा सकता है. इसमें ज़रूरी कॉलम की संख्या शामिल होती है.
अगर आपके डिज़ाइन में सिर्फ़ कुछ आइटम के लिए नॉन-स्टैंडर्ड डाइमेंशन की ज़रूरत है, तो आइटम के लिए कस्टम कॉलम स्पैन देने के लिए, ग्रिड सपोर्ट का इस्तेमाल किया जा सकता है.
item
और items
तरीकों के LazyGridScope DSL
के span
पैरामीटर की मदद से, कॉलम स्पैन तय करें.
maxLineSpan
, स्पैन स्कोप की वैल्यू में से एक है. यह तब खास तौर पर काम आती है, जब अडैप्टिव साइज़िंग का इस्तेमाल किया जा रहा हो. ऐसा इसलिए, क्योंकि कॉलम की संख्या तय नहीं होती.
इस उदाहरण में, पूरी लाइन का स्पैन देने का तरीका बताया गया है:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 30.dp) ) { item(span = { // LazyGridItemSpanScope: // maxLineSpan GridItemSpan(maxLineSpan) }) { CategoryCard("Fruits") } // ... }
लेज़ी स्टैगर्ड ग्रिड
LazyVerticalStaggeredGrid
और LazyHorizontalStaggeredGrid
कंपोज़ेबल हैं. इनकी मदद से, आइटम की लेज़ी-लोडेड, स्टैगर्ड ग्रिड बनाई जा सकती है.
लेज़ी वर्टिकल स्टैगर्ड ग्रिड, अपने आइटम को वर्टिकल तौर पर स्क्रोल किए जा सकने वाले कंटेनर में दिखाता है. यह कंटेनर कई कॉलम में फैला होता है और अलग-अलग आइटम की ऊंचाई अलग-अलग होती है. हॉरिजॉन्टल लेज़ी ग्रिड, हॉरिज़ॉन्टल ऐक्सिस पर एक जैसा काम करती हैं. हालांकि, इनमें अलग-अलग चौड़ाई वाले आइटम होते हैं.
यहां दिए गए स्निपेट में, LazyVerticalStaggeredGrid
का इस्तेमाल करने का एक बुनियादी उदाहरण दिया गया है. इसमें हर आइटम के लिए 200.dp
की चौड़ाई तय की गई है:
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() )

कॉन्टेंट पैडिंग
कभी-कभी आपको कॉन्टेंट के किनारों पर पैडिंग जोड़ने की ज़रूरत पड़ सकती है. लेज़ी कंपोनेंट की मदद से, contentPadding
पैरामीटर में कुछ PaddingValues
पास किए जा सकते हैं, ताकि इस सुविधा का इस्तेमाल किया जा सके:
LazyColumn( contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), ) { // ... }
इस उदाहरण में, हमने हॉरिज़ॉन्टल किनारों (बाएं और दाएं) पर 16.dp
पैडिंग जोड़ी है. इसके बाद, हमने कॉन्टेंट के ऊपर और नीचे 8.dp
पैडिंग जोड़ी है.
कृपया ध्यान दें कि यह पैडिंग, कॉन्टेंट पर लागू होती है, न कि LazyColumn
पर. ऊपर दिए गए उदाहरण में, पहले आइटम के ऊपर 8.dp
पैडिंग जोड़ी जाएगी. आखिरी आइटम के नीचे 8.dp
पैडिंग जोड़ी जाएगी. साथ ही, सभी आइटम के बाईं और दाईं ओर 16.dp
पैडिंग होगी.
एक और उदाहरण के तौर पर, Scaffold
के PaddingValues
को LazyColumn
के contentPadding
में पास किया जा सकता है. एंड-टू-एंड गाइड देखें.
कॉन्टेंट के बीच की दूरी
आइटम के बीच स्पेस जोड़ने के लिए, 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
प्रिमिटिव, इनम या पार्सलेबल जैसे टाइप के साथ काम करता है.
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) ) ) { // ... } } }
पक्का करें कि आपने अपने आइटम के लिए कुंजियां दी हों, ताकि ले जाए गए एलिमेंट की नई जगह का पता लगाया जा सके.
उदाहरण: लेज़ी लिस्ट में मौजूद आइटम को एनिमेट करना
Compose की मदद से, लेज़ी लिस्ट में मौजूद आइटम में हुए बदलावों को ऐनिमेट किया जा सकता है. इन स्निपेट का एक साथ इस्तेमाल करने पर, लेज़ी लिस्ट में आइटम जोड़ने, हटाने, और क्रम बदलने पर ऐनिमेशन लागू होते हैं.
इस स्निपेट में, स्ट्रिंग की एक सूची दिखाई गई है. इसमें आइटम जोड़ने, हटाने या क्रम बदलने पर ऐनिमेशन के साथ ट्रांज़िशन होते हैं:
@Composable fun ListAnimatedItems( items: List<String>, modifier: Modifier = Modifier ) { LazyColumn(modifier) { // Use a unique key per item, so that animations work as expected. items(items, key = { it }) { ListItem( headlineContent = { Text(it) }, modifier = Modifier .animateItem( // Optionally add custom animation specs ) .fillParentMaxWidth() .padding(horizontal = 8.dp, vertical = 0.dp), ) } } }
कोड के बारे में अहम जानकारी
ListAnimatedItems
,LazyColumn
में स्ट्रिंग की सूची दिखाता है. साथ ही, आइटम में बदलाव होने पर ऐनिमेशन के साथ ट्रांज़िशन दिखाता है.items
फ़ंक्शन, सूची में मौजूद हर आइटम को एक यूनीक कुंजी असाइन करता है. Compose, आइटम को ट्रैक करने और उनकी जगह में हुए बदलावों की पहचान करने के लिए कुंजियों का इस्तेमाल करता है.ListItem
से, सूची में मौजूद हर आइटम का लेआउट तय होता है. यहheadlineContent
पैरामीटर लेता है, जो आइटम के मुख्य कॉन्टेंट के बारे में बताता है.animateItem
मॉडिफ़ायर, आइटम जोड़ने, हटाने, और उनकी जगह बदलने पर डिफ़ॉल्ट ऐनिमेशन लागू करता है.
यहां दिए गए स्निपेट में, एक ऐसी स्क्रीन दिखाई गई है जिसमें आइटम जोड़ने और हटाने के कंट्रोल शामिल हैं. साथ ही, इसमें पहले से तय की गई सूची को क्रम से लगाने की सुविधा भी है:
@Composable private fun ListAnimatedItemsExample( data: List<String>, modifier: Modifier = Modifier, onAddItem: () -> Unit = {}, onRemoveItem: () -> Unit = {}, resetOrder: () -> Unit = {}, onSortAlphabetically: () -> Unit = {}, onSortByLength: () -> Unit = {}, ) { val canAddItem = data.size < 10 val canRemoveItem = data.isNotEmpty() Scaffold(modifier) { paddingValues -> Column( modifier = Modifier .padding(paddingValues) .fillMaxSize() ) { // Buttons that change the value of displayedItems. AddRemoveButtons(canAddItem, canRemoveItem, onAddItem, onRemoveItem) OrderButtons(resetOrder, onSortAlphabetically, onSortByLength) // List that displays the values of displayedItems. ListAnimatedItems(data) } } }
कोड के बारे में अहम जानकारी
ListAnimatedItemsExample
एक ऐसी स्क्रीन दिखाता है जिसमें आइटम जोड़ने, हटाने, और क्रम से लगाने के कंट्रोल शामिल होते हैं.onAddItem
औरonRemoveItem
, लैम्डा एक्सप्रेशन हैं. इन्हेंAddRemoveButtons
में पास किया जाता है, ताकि सूची में आइटम जोड़े और हटाए जा सकें.resetOrder
,onSortAlphabetically
, औरonSortByLength
, लैम्डा एक्सप्रेशन हैं. इन्हेंOrderButtons
को पास किया जाता है, ताकि सूची में मौजूद आइटम का क्रम बदला जा सके.
AddRemoveButtons
में "जोड़ें" और "हटाएं" बटन दिखते हैं. यह कुकी, बटन को चालू/बंद करती है और बटन पर होने वाले क्लिक को मैनेज करती है.OrderButtons
में, सूची का क्रम बदलने के लिए बटन दिखते हैं. यह फ़ंक्शन, क्रम को रीसेट करने और सूची को लंबाई या वर्णमाला के हिसाब से क्रम में लगाने के लिए लैम्ब्डा फ़ंक्शन लेता है.ListAnimatedItems
,ListAnimatedItems
कंपोज़ेबल को कॉल करता है. साथ ही, स्ट्रिंग की ऐनिमेटेड सूची दिखाने के लिए,data
सूची को पास करता है.data
को कहीं और तय किया गया है.
इस स्निपेट से, आइटम जोड़ें और आइटम मिटाएं बटन वाला यूज़र इंटरफ़ेस (यूआई) बनता है:
@Composable private fun AddRemoveButtons( canAddItem: Boolean, canRemoveItem: Boolean, onAddItem: () -> Unit, onRemoveItem: () -> Unit ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { Button(enabled = canAddItem, onClick = onAddItem) { Text("Add Item") } Spacer(modifier = Modifier.padding(25.dp)) Button(enabled = canRemoveItem, onClick = onRemoveItem) { Text("Delete Item") } } }
कोड के बारे में अहम जानकारी
AddRemoveButtons
में, सूची में आइटम जोड़ने और हटाने के लिए बटनों की एक लाइन दिखती है.canAddItem
औरcanRemoveItem
पैरामीटर, बटन की चालू स्थिति को कंट्रोल करते हैं. अगरcanAddItem
याcanRemoveItem
को 'गलत है' पर सेट किया जाता है, तो इससे जुड़ा बटन बंद हो जाता है.onAddItem
औरonRemoveItem
पैरामीटर, लैम्डा होते हैं. ये तब काम करते हैं, जब उपयोगकर्ता संबंधित बटन पर क्लिक करता है.
आखिर में, इस स्निपेट में सूची को क्रम से लगाने के लिए तीन बटन दिखते हैं: रीसेट करें, वर्णमाला के क्रम में लगाएं और अवधि:
@Composable private fun OrderButtons( resetOrder: () -> Unit, orderAlphabetically: () -> Unit, orderByLength: () -> Unit ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { var selectedIndex by remember { mutableIntStateOf(0) } val options = listOf("Reset", "Alphabetical", "Length") SingleChoiceSegmentedButtonRow { options.forEachIndexed { index, label -> SegmentedButton( shape = SegmentedButtonDefaults.itemShape( index = index, count = options.size ), onClick = { Log.d("AnimatedOrderedList", "selectedIndex: $selectedIndex") selectedIndex = index when (options[selectedIndex]) { "Reset" -> resetOrder() "Alphabetical" -> orderAlphabetically() "Length" -> orderByLength() } }, selected = index == selectedIndex ) { Text(label) } } } } }
कोड के बारे में अहम जानकारी
OrderButtons
, उपयोगकर्ताओं को सूची में क्रम बदलने का तरीका चुनने या सूची के क्रम को रीसेट करने की अनुमति देने के लिएSingleChoiceSegmentedButtonRow
दिखाता है.SegmentedButton
कॉम्पोनेंट की मदद से, विकल्पों की सूची में से कोई एक विकल्प चुना जा सकता है.resetOrder
,orderAlphabetically
, औरorderByLength
, लैम्डा फ़ंक्शन हैं. ये फ़ंक्शन, इनसे जुड़े बटन को चुनने पर काम करते हैं.selectedIndex
स्टेट वैरिएबल, चुने गए विकल्प को ट्रैक करता है.
नतीजा
इस वीडियो में, आइटम का क्रम बदलने पर ऊपर दिए गए स्निपेट के नतीजे दिखाए गए हैं:
स्टिकी हेडर (एक्सपेरिमेंट के तौर पर)
ग्रुप किए गए डेटा की सूचियां दिखाने के लिए, ‘स्टिक हेडर’ पैटर्न मददगार होता है. यहां ‘संपर्क सूची’ का एक उदाहरण दिया गया है. इसमें हर संपर्क के नाम के पहले अक्षर के हिसाब से ग्रुप बनाए गए हैं:
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) } } ) }
बड़े डेटासेट (पेजिंग)
पेजिंग लाइब्रेरी की मदद से ऐप्लिकेशन, आइटम की बड़ी सूचियों को सपोर्ट कर सकते हैं. साथ ही, ज़रूरत के हिसाब से सूची के छोटे-छोटे हिस्से लोड और दिखाए जा सकते हैं. Paging 3.0 और इसके बाद के वर्शन, androidx.paging:paging-compose
लाइब्रेरी की मदद से Compose के साथ काम करते हैं.
पेज वाले कॉन्टेंट की सूची दिखाने के लिए, हम collectAsLazyPagingItems()
एक्सटेंशन फ़ंक्शन का इस्तेमाल कर सकते हैं. इसके बाद, LazyColumn
में items()
को LazyPagingItems
पास करें. व्यू में पेजिंग की सुविधा की तरह ही, डेटा लोड होने के दौरान प्लेसहोल्डर दिखाए जा सकते हैं. इसके लिए, यह देखना होगा कि 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 पिक्सल है और वह उन सभी को व्यूपोर्ट में फ़िट कर सकता है. आइटम लोड होने और उनकी ऊंचाई बढ़ने के बाद, लेज़ी लेआउट उन सभी आइटम को खारिज कर देगा जिन्हें पहली बार में बिना किसी वजह के कंपोज़ किया गया था. ऐसा इसलिए, क्योंकि वे व्यूपोर्ट में फ़िट नहीं हो सकते. इससे बचने के लिए, आपको अपने आइटम के लिए डिफ़ॉल्ट साइज़ सेट करना चाहिए, ताकि लेज़ी लेआउट यह सही तरीके से हिसाब लगा सके कि व्यूपोर्ट में कितने आइटम फ़िट हो सकते हैं:
@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
जोड़ें
Compose 1.2 से शुरू करके, Lazy
layout की परफ़ॉर्मेंस को बेहतर बनाने के लिए, अपनी सूचियों या ग्रिड में contentType
जोड़ें. इससे, लेआउट के हर आइटम के लिए कॉन्टेंट टाइप तय किया जा सकता है. ऐसा तब किया जाता है, जब आपको अलग-अलग तरह के आइटम वाली कोई सूची या ग्रिड बनानी हो:
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
contentType
उपलब्ध कराने पर, Compose सिर्फ़ एक ही तरह के आइटम के बीच कंपोज़िशन का फिर से इस्तेमाल कर पाता है. मिलते-जुलते स्ट्रक्चर वाले आइटम को फिर से इस्तेमाल करना ज़्यादा असरदार होता है. इसलिए, कॉन्टेंट टाइप की जानकारी देने से यह पक्का होता है कि Compose, टाइप A वाले आइटम के ऊपर टाइप B वाला आइटम न बना दे. इससे कंपोज़िशन को फिर से इस्तेमाल करने के फ़ायदों को ज़्यादा से ज़्यादा बढ़ाने में मदद मिलती है. साथ ही, लेज़ी लेआउट की परफ़ॉर्मेंस को बेहतर बनाने में मदद मिलती है.
परफ़ॉर्मेंस को मेज़र करना
लेज़ी लेआउट की परफ़ॉर्मेंस का सही आकलन सिर्फ़ तब किया जा सकता है, जब वह रिलीज़ मोड में चल रहा हो और R8 ऑप्टिमाइज़ेशन चालू हो. डीबग बिल्ड पर, लेज़ी लेआउट स्क्रोलिंग की स्पीड कम हो सकती है. इस बारे में ज़्यादा जानने के लिए, ईमेल लिखने की परफ़ॉर्मेंस लेख पढ़ें.
अन्य संसाधन
- स्क्रोल की जा सकने वाली सीमित सूची बनाना
- स्क्रोल की जा सकने वाली ग्रिड बनाना
- नेस्ट किए गए स्क्रोलिंग आइटम को सूची में दिखाना
- टाइप करते समय सूची को फ़िल्टर करना
- सूचियों और पेजिंग की मदद से डेटा को धीरे-धीरे लोड करना
- एक से ज़्यादा तरह के आइटम का इस्तेमाल करके सूची बनाना
- वीडियो: कंपोज़ करते समय सूचियां बनाना
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक टेक्स्ट दिखता है
RecyclerView
को Lazy list में माइग्रेट करना- Compose में यूज़र इंटरफ़ेस (यूआई) की स्थिति सेव करना
- Jetpack Compose के लिए Kotlin