स्क्रोल मॉडिफ़ायर
verticalScroll और horizontalScroll मॉडिफ़ायर, उपयोगकर्ता को किसी एलिमेंट को स्क्रोल करने की अनुमति देने का सबसे आसान तरीका है. ऐसा तब होता है, जब उसके कॉन्टेंट की सीमाएं, उसके साइज़ की ज़्यादा से ज़्यादा सीमाओं से ज़्यादा होती हैं. verticalScroll और horizontalScroll मॉडिफ़ायर की मदद से, आपको कॉन्टेंट का अनुवाद करने या उसे ऑफ़सेट करने की ज़रूरत नहीं होती.
@Composable private fun ScrollBoxes() { Column( modifier = Modifier .background(Color.LightGray) .size(100.dp) .verticalScroll(rememberScrollState()) ) { repeat(10) { Text("Item $it", modifier = Modifier.padding(2.dp)) } } }
ScrollState की मदद से, स्क्रोल करने की जगह बदली जा सकती है या उसकी मौजूदा स्थिति पता की जा सकती है. डिफ़ॉल्ट पैरामीटर के साथ इसे बनाने के लिए, rememberScrollState() का इस्तेमाल करें.
@Composable private fun ScrollBoxesSmooth() { // Smoothly scroll 100px on first composition val state = rememberScrollState() LaunchedEffect(Unit) { state.animateScrollTo(100) } Column( modifier = Modifier .background(Color.LightGray) .size(100.dp) .padding(horizontal = 8.dp) .verticalScroll(state) ) { repeat(10) { Text("Item $it", modifier = Modifier.padding(2.dp)) } } }
स्क्रोल किए जा सकने वाले मॉडिफ़ायर
scrollable मॉडिफ़ायर, स्क्रोल मॉडिफ़ायर से इस मामले में अलग है कि scrollable स्क्रोल के जेस्चर का पता लगाता है और डेल्टा कैप्चर करता है. हालांकि, यह अपने कॉन्टेंट को अपने-आप ऑफ़सेट नहीं करता है. इसके बजाय, यह अधिकार उपयोगकर्ता को ScrollableState के ज़रिए दिया जाता है. इस मॉडिफ़ायर के सही तरीके से काम करने के लिए, यह ज़रूरी है.
ScrollableState बनाते समय, आपको एक ScrollableState फ़ंक्शन देना होगा. इस फ़ंक्शन को हर स्क्रोल स्टेप पर (हाथ के जेस्चर, स्मूथ स्क्रोलिंग या फ़्लिंग के ज़रिए) पिक्सल में डेल्टा के साथ लागू किया जाएगा.consumeScrollDelta इस फ़ंक्शन को स्क्रोल की गई दूरी की जानकारी देनी चाहिए, ताकि यह पक्का किया जा सके कि इवेंट को सही तरीके से ट्रिगर किया गया है. ऐसा उन मामलों में ज़रूरी है जहां नेस्ट किए गए ऐसे एलिमेंट मौजूद हों जिनमें scrollable मॉडिफ़ायर मौजूद हो.
यहां दिए गए स्निपेट में, जेस्चर का पता लगाया जाता है और ऑफ़सेट के लिए संख्या वाली वैल्यू दिखाई जाती है. हालांकि, इससे किसी भी एलिमेंट को ऑफ़सेट नहीं किया जाता:
@Composable private fun ScrollableSample() { // actual composable state var offset by remember { mutableFloatStateOf(0f) } Box( Modifier .size(150.dp) .scrollable( orientation = Orientation.Vertical, // Scrollable state: describes how to consume // scrolling delta and update offset state = rememberScrollableState { delta -> offset += delta delta } ) .background(Color.LightGray), contentAlignment = Alignment.Center ) { Text(offset.toString()) } }
नेस्ट की गई स्क्रोलिंग
नेस्टेड स्क्रोलिंग एक ऐसा सिस्टम है जिसमें एक-दूसरे के अंदर मौजूद कई स्क्रोलिंग कॉम्पोनेंट, एक साथ काम करते हैं. ये कॉम्पोनेंट, स्क्रोल करने के एक ही जेस्चर पर प्रतिक्रिया देते हैं और स्क्रोलिंग के डेल्टा (बदलाव) को कम्यूनिकेट करते हैं.
नेस्टेड स्क्रोलिंग सिस्टम, स्क्रोल किए जा सकने वाले और क्रम से जुड़े कॉम्पोनेंट के बीच तालमेल बिठाने की सुविधा देता है. ये कॉम्पोनेंट, अक्सर एक ही पैरंट को शेयर करते हैं. यह सिस्टम, स्क्रोल करने वाले कंटेनर को लिंक करता है. साथ ही, स्क्रोल करने वाले डेल्टा के साथ इंटरैक्ट करने की अनुमति देता है. ये डेल्टा, कंटेनर के बीच शेयर किए जाते हैं.
Compose, कंपोज़ेबल के बीच नेस्ट की गई स्क्रोलिंग को मैनेज करने के कई तरीके उपलब्ध कराता है. नेस्टेड स्क्रोलिंग का एक सामान्य उदाहरण, किसी सूची के अंदर मौजूद दूसरी सूची है. वहीं, इसका एक ज़्यादा जटिल उदाहरण कोलैप्स होने वाला टूलबार है.
नेस्ट किए गए स्क्रोलिंग की सुविधा अपने-आप चालू होना
सिंपल नेस्टेड स्क्रोलिंग के लिए, आपको कुछ नहीं करना है. स्क्रोल करने की सुविधा शुरू करने वाले जेस्चर, बच्चों से माता-पिता को अपने-आप मिल जाते हैं. ऐसा इसलिए होता है, ताकि जब बच्चा आगे स्क्रोल न कर पाए, तो जेस्चर को उसके पैरंट एलिमेंट से मैनेज किया जा सके.
नेस्टेड स्क्रोलिंग की सुविधा, Compose के कुछ कॉम्पोनेंट और मॉडिफ़ायर में पहले से मौजूद होती है. जैसे:
verticalScroll,
horizontalScroll,
scrollable,
Lazy एपीआई और TextField. इसका मतलब है कि जब उपयोगकर्ता नेस्ट किए गए कॉम्पोनेंट के किसी इनर चाइल्ड को स्क्रोल करता है, तो पिछले मॉडिफ़ायर, स्क्रोलिंग डेल्टा को उन पैरंट कॉम्पोनेंट तक पहुंचाते हैं जिनमें नेस्ट की गई स्क्रोलिंग की सुविधा होती है.
इस उदाहरण में, ऐसे एलिमेंट दिखाए गए हैं जिन पर verticalScroll मॉडिफ़ायर लागू किया गया है. ये एलिमेंट, ऐसे कंटेनर में मौजूद हैं जिस पर verticalScroll मॉडिफ़ायर लागू किया गया है.
@Composable private fun AutomaticNestedScroll() { val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White) Box( modifier = Modifier .background(Color.LightGray) .verticalScroll(rememberScrollState()) .padding(32.dp) ) { Column { repeat(6) { Box( modifier = Modifier .height(128.dp) .verticalScroll(rememberScrollState()) ) { Text( "Scroll here", modifier = Modifier .border(12.dp, Color.DarkGray) .background(brush = gradient) .padding(24.dp) .height(150.dp) ) } } } } }
nestedScroll मॉडिफ़ायर का इस्तेमाल करना
अगर आपको कई एलिमेंट के बीच बेहतर तरीके से कोऑर्डिनेट की गई स्क्रोलिंग बनानी है, तो nestedScroll मॉडिफ़ायर का इस्तेमाल करें. इससे आपको नेस्ट की गई स्क्रोलिंग की हैरारकी तय करने में ज़्यादा आसानी होती है. पिछले सेक्शन में बताया गया है कि कुछ कॉम्पोनेंट में, नेस्ट किए गए स्क्रोल करने की सुविधा पहले से मौजूद होती है. हालांकि, अपने-आप स्क्रोल न होने वाले कंपोज़ेबल के लिए, जैसे कि Box या Column, ऐसे कॉम्पोनेंट पर स्क्रोल डेल्टा, नेस्ट किए गए स्क्रोल सिस्टम में नहीं फैलेंगे. साथ ही, डेल्टा NestedScrollConnection या पैरंट कॉम्पोनेंट तक नहीं पहुंचेंगे. इस समस्या को हल करने के लिए, nestedScroll का इस्तेमाल करके, कस्टम कॉम्पोनेंट के साथ-साथ अन्य कॉम्पोनेंट को भी यह सुविधा दी जा सकती है.
नेस्ट की गई स्क्रोलिंग साइकल
नेस्टेड स्क्रोल साइकल, स्क्रोल डेल्टा का फ़्लो होता है. इसे नेस्टेड स्क्रोलिंग सिस्टम का हिस्सा बनने वाले सभी कॉम्पोनेंट (या नोड) के ज़रिए, हैरारकी ट्री में ऊपर और नीचे भेजा जाता है. उदाहरण के लिए, स्क्रोल किए जा सकने वाले कॉम्पोनेंट और मॉडिफ़ायर या nestedScroll का इस्तेमाल करके.
नेस्ट की गई स्क्रोलिंग के साइकल के फ़ेज़
जब स्क्रोल किए जा सकने वाले कॉम्पोनेंट को ट्रिगर इवेंट (उदाहरण के लिए, कोई जेस्चर) का पता चलता है, तो स्क्रोल करने की कार्रवाई शुरू होने से पहले ही, जनरेट किए गए डेल्टा को नेस्ट किए गए स्क्रोल सिस्टम को भेज दिया जाता है. इसके बाद, ये तीन चरणों से गुज़रते हैं: प्री-स्क्रोल, नोड कंजम्पशन, और पोस्ट-स्क्रोल.
पहले, प्री-स्क्रोल फ़ेज़ में, जिस कॉम्पोनेंट को ट्रिगर इवेंट डेल्टा मिले हैं वह उन इवेंट को, हैरारकी ट्री के ज़रिए सबसे ऊपर मौजूद पैरंट तक भेजेगा. इसके बाद, डेल्टा इवेंट नीचे की ओर जाएंगे. इसका मतलब है कि डेल्टा, सबसे ऊपर मौजूद पैरंट से लेकर उस चाइल्ड तक भेजे जाएंगे जिसने नेस्ट किए गए स्क्रोल साइकल को शुरू किया था.
इससे नेस्ट किए गए स्क्रोल के पैरंट (nestedScroll या स्क्रोल किए जा सकने वाले मॉडिफ़ायर का इस्तेमाल करने वाले कंपोज़ेबल) को डेल्टा के साथ कुछ करने का मौका मिलता है. ऐसा तब होता है, जब नोड खुद इसका इस्तेमाल कर पाता है.
नोड के इस्तेमाल के चरण में, नोड खुद उस डेल्टा का इस्तेमाल करेगा जिसे उसके पैरंट ने इस्तेमाल नहीं किया था. इस दौरान, स्क्रोलिंग की प्रोसेस पूरी हो जाती है और यह दिखती है.
इस दौरान, बच्चा बची हुई स्क्रोल का पूरा या कुछ हिस्सा इस्तेमाल कर सकता है. बची हुई सभी चीज़ों को वापस भेज दिया जाएगा, ताकि वे स्क्रोल करने के बाद दिखने वाले फ़ेज़ में जा सकें.
आखिर में, स्क्रोल करने के बाद वाले फ़ेज़ में, नोड ने जो भी इस्तेमाल नहीं किया है उसे उसके पूर्वजों को भेज दिया जाएगा, ताकि वे उसका इस्तेमाल कर सकें.
पोस्ट-स्क्रोल फ़ेज़, प्री-स्क्रोल फ़ेज़ की तरह ही काम करता है. इसमें कोई भी पैरंट, कॉन्टेंट को देख सकता है या नहीं भी देख सकता है.
स्क्रोल करने की तरह ही, खींचकर छोड़ने का जेस्चर खत्म होने पर, उपयोगकर्ता के इरादे को एक वेलोसिटी में बदला जा सकता है. इसका इस्तेमाल, स्क्रोल किए जा सकने वाले कंटेनर को फ़्लिंग करने (ऐनिमेशन का इस्तेमाल करके स्क्रोल करना) के लिए किया जाता है. फ़्लिंग, नेस्ट किए गए स्क्रोल साइकल का भी हिस्सा है. साथ ही, खींचने वाले इवेंट से जनरेट हुई वेलोसिटी, एक जैसी फ़ेज़ से गुज़रती है: प्री-फ़्लिंग, नोड कंजम्पशन, और पोस्ट-फ़्लिंग. ध्यान दें कि फ़्लिंग ऐनिमेशन सिर्फ़ टच जेस्चर से जुड़ा होता है. यह a11y या हार्डवेयर स्क्रोल जैसे अन्य इवेंट से ट्रिगर नहीं होगा.
नेस्टेड स्क्रोलिंग साइकल में हिस्सा लेना
साइकल में हिस्सा लेने का मतलब है, हैरारकी के हिसाब से डेल्टा को इंटरसेप्ट करना, उनका इस्तेमाल करना, और उनके इस्तेमाल की रिपोर्ट करना. Compose, नेस्ट किए गए स्क्रोलिंग सिस्टम के काम करने के तरीके और उससे सीधे तौर पर इंटरैक्ट करने के तरीके को बेहतर बनाने के लिए टूल का एक सेट उपलब्ध कराता है. उदाहरण के लिए, जब आपको स्क्रोल किए जा सकने वाले कॉम्पोनेंट के स्क्रोल करना शुरू करने से पहले, स्क्रोल डेल्टा के साथ कुछ करना हो.
अगर नेस्ट किया गया स्क्रोल साइकल, नोड की चेन पर काम करने वाला सिस्टम है, तो nestedScroll मॉडिफ़ायर, इन बदलावों को रोकने और उनमें बदलाव करने का एक तरीका है. साथ ही, यह चेन में भेजे गए डेटा (स्क्रोल डेल्टा) को भी प्रभावित करता है. इस मॉडिफ़ायर को क्रम में कहीं भी रखा जा सकता है. यह ट्री में मौजूद नेस्ट किए गए स्क्रोल मॉडिफ़ायर इंस्टेंस के साथ कम्यूनिकेट करता है, ताकि इस चैनल के ज़रिए जानकारी शेयर की जा सके. इस मॉडिफ़ायर के बिल्डिंग ब्लॉक NestedScrollConnection और NestedScrollDispatcher हैं.
NestedScrollConnection
नेस्ट किए गए स्क्रोल साइकल के फ़ेज़ के हिसाब से जवाब देने का तरीका बताता है. साथ ही, नेस्ट किए गए स्क्रोल सिस्टम पर असर डालता है. इसमें चार कॉलबैक तरीके होते हैं. हर तरीका, खपत के एक चरण को दिखाता है: स्क्रोल करने से पहले/बाद और फ़्लिंग करने से पहले/बाद:
val nestedScrollConnection = object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { println("Received onPreScroll callback.") return Offset.Zero } override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { println("Received onPostScroll callback.") return Offset.Zero } }
हर कॉलबैक में, डेल्टा के बारे में भी जानकारी मिलती है. डेल्टा का मतलब है कि किसी खास फ़ेज़ में कितना डेटा प्रोसेस किया गया और पिछले फ़ेज़ में कितना डेटा प्रोसेस किया गया:
available उस फ़ेज़ के लिए डेल्टा और consumed पिछले फ़ेज़ में इस्तेमाल किया गया डेल्टा. अगर आपको किसी भी समय, डेल्टा को हाइरार्की में ऊपर की ओर ले जाने से रोकना है, तो इसके लिए नेस्ट किए गए स्क्रोल कनेक्शन का इस्तेमाल करें:
val disabledNestedScrollConnection = remember { object : NestedScrollConnection { override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { return if (source == NestedScrollSource.SideEffect) { available } else { Offset.Zero } } } }
सभी कॉलबैक, NestedScrollSource टाइप के बारे में जानकारी देते हैं.
NestedScrollDispatcher नेस्ट किए गए स्क्रोल साइकल को शुरू करता है. डिसपैचर का इस्तेमाल करने और उसके तरीकों को कॉल करने से, साइकल ट्रिगर हो जाती है. स्क्रोल किए जा सकने वाले कंटेनर में एक बिल्ट-इन डिस्पैचर होता है. यह डिस्पैचर, सिस्टम में जेस्चर के दौरान कैप्चर किए गए डेल्टा भेजता है. इस वजह से, नेस्टेड स्क्रोलिंग को पसंद के मुताबिक बनाने के ज़्यादातर इस्तेमाल के मामलों में, नए डेल्टा भेजने के बजाय, पहले से मौजूद डेल्टा पर प्रतिक्रिया देने के लिए, डिस्पैचर के बजाय NestedScrollConnection का इस्तेमाल किया जाता है.
इन्हें इस्तेमाल करने के अन्य तरीकों के बारे में जानने के लिए, NestedScrollDispatcherSample पर जाएं.
स्क्रोल करने पर इमेज का साइज़ बदलना
उपयोगकर्ता के स्क्रोल करने पर, डाइनैमिक विज़ुअल इफ़ेक्ट बनाया जा सकता है. इसमें स्क्रोल की पोज़िशन के हिसाब से इमेज का साइज़ बदलता है.
स्क्रोल की पोज़िशन के आधार पर किसी इमेज का साइज़ बदलना
इस स्निपेट में, वर्टिकल स्क्रोल की पोज़िशन के आधार पर, LazyColumn में मौजूद इमेज का साइज़ बदलने का तरीका दिखाया गया है. उपयोगकर्ता के नीचे की ओर स्क्रोल करने पर इमेज छोटी हो जाती है और ऊपर की ओर स्क्रोल करने पर बड़ी हो जाती है. हालांकि, इमेज का साइज़ तय की गई कम से कम और ज़्यादा से ज़्यादा सीमा के अंदर ही रहता है:
@Composable fun ImageResizeOnScrollExample( modifier: Modifier = Modifier, maxImageSize: Dp = 300.dp, minImageSize: Dp = 100.dp ) { var currentImageSize by remember { mutableStateOf(maxImageSize) } var imageScale by remember { mutableFloatStateOf(1f) } val nestedScrollConnection = remember { object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { // Calculate the change in image size based on scroll delta val delta = available.y val newImageSize = currentImageSize + delta.dp val previousImageSize = currentImageSize // Constrain the image size within the allowed bounds currentImageSize = newImageSize.coerceIn(minImageSize, maxImageSize) val consumed = currentImageSize - previousImageSize // Calculate the scale for the image imageScale = currentImageSize / maxImageSize // Return the consumed scroll amount return Offset(0f, consumed.value) } } } Box(Modifier.nestedScroll(nestedScrollConnection)) { LazyColumn( Modifier .fillMaxWidth() .padding(15.dp) .offset { IntOffset(0, currentImageSize.roundToPx()) } ) { // Placeholder list items items(100, key = { it }) { Text( text = "Item: $it", style = MaterialTheme.typography.bodyLarge ) } } Image( painter = ColorPainter(Color.Red), contentDescription = "Red color image", Modifier .size(maxImageSize) .align(Alignment.TopCenter) .graphicsLayer { scaleX = imageScale scaleY = imageScale // Center the image vertically as it scales translationY = -(maxImageSize.toPx() - currentImageSize.toPx()) / 2f } ) } }
कोड के बारे में मुख्य बातें
- यह कोड, स्क्रोल इवेंट को इंटरसेप्ट करने के लिए
NestedScrollConnectionका इस्तेमाल करता है. onPreScroll, स्क्रोल डेल्टा के आधार पर इमेज के साइज़ में होने वाले बदलाव का हिसाब लगाता है.currentImageSizeस्टेट वैरिएबल, इमेज के मौजूदा साइज़ को सेव करता है. यहminImageSizeऔरmaxImageSize. imageScaleके बीच सीमित होता है. यहcurrentImageSizeसे मिलता है.LazyColumn,currentImageSizeके आधार पर ऑफ़सेट करता है.Image, कैलकुलेट किए गए स्केल को लागू करने के लिएgraphicsLayerमॉडिफ़ायर का इस्तेमाल करता है.graphicsLayerमें मौजूदtranslationYयह पक्का करता है कि इमेज का साइज़ बदलते समय, वह लंबवत रूप से बीच में रहे.
नतीजा
ऊपर दिए गए स्निपेट से, स्क्रोल करने पर इमेज के साइज़ में बदलाव होता है:
नेस्टेड स्क्रोलिंग इंटरऑप
स्क्रोल किए जा सकने वाले कंपोज़ेबल में स्क्रोल किए जा सकने वाले View एलिमेंट नेस्ट करने या इसके उलट करने पर, आपको समस्याएं आ सकती हैं. सबसे ज़्यादा ध्यान देने वाली समस्याएं तब होती हैं, जब बच्चे को स्क्रोल करके उसके शुरू या खत्म होने की सीमा तक पहुंचा जाता है और यह उम्मीद की जाती है कि पैरंट, स्क्रोल करने की सुविधा को चालू कर देगा. हालांकि, ऐसा हो सकता है कि यह सुविधा काम न करे या उम्मीद के मुताबिक काम न करे.
यह समस्या, स्क्रोल किए जा सकने वाले कंपोज़ेबल में तय की गई उम्मीदों की वजह से होती है.
स्क्रोल किए जा सकने वाले कंपोज़ेबल में "nested-scroll-by-default" नियम होता है. इसका मतलब है कि स्क्रोल किए जा सकने वाले किसी भी कंटेनर को नेस्ट किए गए स्क्रोल चेन में शामिल होना चाहिए. ऐसा पैरंट के तौर पर NestedScrollConnection और चाइल्ड के तौर पर NestedScrollDispatcher के ज़रिए किया जा सकता है.
इसके बाद, जब चाइल्ड कॉम्पोनेंट बाउंड्री पर होता है, तब वह पैरंट कॉम्पोनेंट के लिए नेस्टेड स्क्रोल को ट्रिगर करता है. उदाहरण के लिए, इस नियम से Compose Pager और Compose LazyRow को एक साथ बेहतर तरीके से काम करने में मदद मिलती है. हालांकि, जब ViewPager2 या RecyclerView के साथ इंटरऑपरेबिलिटी स्क्रोलिंग की जा रही हो, तब चाइल्ड से पैरंट तक लगातार स्क्रोलिंग नहीं की जा सकती. ऐसा इसलिए है, क्योंकि ये NestedScrollingParent3 को लागू नहीं करते हैं.
स्क्रोल किए जा सकने वाले View एलिमेंट और स्क्रोल किए जा सकने वाले कंपोज़ेबल के बीच, नेस्टेड स्क्रोलिंग इंटरऑप एपीआई को चालू किया जा सकता है. ये दोनों एलिमेंट, दोनों दिशाओं में नेस्ट किए गए होते हैं. इन समस्याओं को कम करने के लिए, नेस्टेड स्क्रोलिंग इंटरऑप एपीआई का इस्तेमाल किया जा सकता है. ऐसा इन स्थितियों में किया जा सकता है.
माता-पिता View और बच्चा ComposeView
सहयोग करने वाला अभिभावक View वह होता है जो पहले से ही NestedScrollingParent3 लागू करता है. इसलिए, वह सहयोग करने वाले नेस्ट किए गए चाइल्ड कंपोज़ेबल से स्क्रोलिंग डेल्टा पा सकता है. ComposeView इस मामले में चाइल्ड के तौर पर काम करेगा और उसे NestedScrollingChild3 को (अप्रत्यक्ष रूप से) लागू करना होगा.
androidx.coordinatorlayout.widget.CoordinatorLayout, एक साथ काम करने वाले पैरंट का एक उदाहरण है.
अगर आपको स्क्रोल किए जा सकने वाले View पैरंट कंटेनर और स्क्रोल किए जा सकने वाले नेस्ट किए गए चाइल्ड कंपोज़ेबल के बीच नेस्टेड स्क्रोलिंग इंटरऑपरेबिलिटी की ज़रूरत है, तो rememberNestedScrollInteropConnection() का इस्तेमाल किया जा सकता है.
rememberNestedScrollInteropConnection()
कुकी, NestedScrollConnection को अनुमति देती है और इसे सेव करती है. NestedScrollConnection, View पैरंट और Compose चाइल्ड के बीच नेस्ट किए गए स्क्रोल की इंटरऑपरेबिलिटी को चालू करता है. View पैरंट, NestedScrollingParent3 को लागू करता है. इसका इस्तेमाल nestedScroll मॉडिफ़ायर के साथ किया जाना चाहिए. Compose में नेस्टेड स्क्रोलिंग की सुविधा डिफ़ॉल्ट रूप से चालू होती है. इसलिए, इस कनेक्शन का इस्तेमाल करके, View में नेस्टेड स्क्रोलिंग की सुविधा चालू की जा सकती है. साथ ही, Views और कंपोज़ेबल के बीच ज़रूरी ग्लू लॉजिक जोड़ा जा सकता है.
CoordinatorLayout, CollapsingToolbarLayout, और चाइल्ड कंपोज़ेबल का इस्तेमाल करने का एक सामान्य उदाहरण यहां दिया गया है:
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="100dp" android:fitsSystemWindows="true"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!--...--> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_width="match_parent" android:layout_height="match_parent"/> </androidx.coordinatorlayout.widget.CoordinatorLayout>
आपको अपनी गतिविधि या फ़्रैगमेंट में, बच्चे के लिए कंपोज़ेबल और ज़रूरी NestedScrollConnection सेट अप करना होगा:
open class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<ComposeView>(R.id.compose_view).apply { setContent { val nestedScrollInterop = rememberNestedScrollInteropConnection() // Add the nested scroll connection to your top level @Composable element // using the nestedScroll modifier. LazyColumn(modifier = Modifier.nestedScroll(nestedScrollInterop)) { items(20) { item -> Box( modifier = Modifier .padding(16.dp) .height(56.dp) .fillMaxWidth() .background(Color.Gray), contentAlignment = Alignment.Center ) { Text(item.toString()) } } } } } } }
चाइल्ड AndroidView को शामिल करने वाला पैरंट कंपोज़ेबल
इस उदाहरण में, Compose साइड पर नेस्ट की गई स्क्रोलिंग इंटरऑप एपीआई को लागू करने के बारे में बताया गया है. ऐसा तब होता है, जब आपके पास पैरंट कंपोज़ेबल होता है, जिसमें चाइल्ड AndroidView होता है. AndroidView, NestedScrollDispatcher को लागू करता है, क्योंकि यह Compose के स्क्रोल करने वाले पैरंट के लिए चाइल्ड के तौर पर काम करता है. साथ ही, यह NestedScrollingParent3 को भी लागू करता है, क्योंकि यह View के स्क्रोल करने वाले चाइल्ड के लिए पैरंट के तौर पर काम करता है. इसके बाद, पैरंट कंपोज़िशन को नेस्ट किए गए स्क्रोल करने लायक चाइल्ड View से, नेस्ट किए गए स्क्रोल के डेल्टा मिल पाएंगे.
यहां दिए गए उदाहरण में, इस स्थिति में नेस्ट की गई स्क्रोलिंग को इंटरऑप करने का तरीका बताया गया है. साथ ही, इसमें Compose की कोलैप्स होने वाली टूलबार भी शामिल है:
@Composable
private fun NestedScrollInteropComposeParentWithAndroidChildExample() {
val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() }
val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }
// Sets up the nested scroll connection between the Box composable parent
// and the child AndroidView containing the RecyclerView
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// Updates the toolbar offset based on the scroll to enable
// collapsible behaviour
val delta = available.y
val newOffset = toolbarOffsetHeightPx.value + delta
toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
return Offset.Zero
}
}
}
Box(
Modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection)
) {
TopAppBar(
modifier = Modifier
.height(ToolbarHeight)
.offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) }
)
AndroidView(
{ context ->
LayoutInflater.from(context)
.inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply {
with(findViewById<RecyclerView>(R.id.main_list)) {
layoutManager = LinearLayoutManager(context, VERTICAL, false)
adapter = NestedScrollInteropAdapter()
}
}.also {
// Nested scrolling interop is enabled when
// nested scroll is enabled for the root View
ViewCompat.setNestedScrollingEnabled(it, true)
}
},
// ...
)
}
}
private class NestedScrollInteropAdapter :
Adapter<NestedScrollInteropAdapter.NestedScrollInteropViewHolder>() {
val items = (1..10).map { it.toString() }
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): NestedScrollInteropViewHolder {
return NestedScrollInteropViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
)
}
override fun onBindViewHolder(holder: NestedScrollInteropViewHolder, position: Int) {
// ...
}
class NestedScrollInteropViewHolder(view: View) : ViewHolder(view) {
fun bind(item: String) {
// ...
}
}
// ...
}
इस उदाहरण में, scrollable मॉडिफ़ायर के साथ एपीआई का इस्तेमाल करने का तरीका दिखाया गया है:
@Composable
fun ViewInComposeNestedScrollInteropExample() {
Box(
Modifier
.fillMaxSize()
.scrollable(rememberScrollableState {
// View component deltas should be reflected in Compose
// components that participate in nested scrolling
it
}, Orientation.Vertical)
) {
AndroidView(
{ context ->
LayoutInflater.from(context)
.inflate(android.R.layout.list_item, null)
.apply {
// Nested scrolling interop is enabled when
// nested scroll is enabled for the root View
ViewCompat.setNestedScrollingEnabled(this, true)
}
}
)
}
}
आखिर में, इस उदाहरण में दिखाया गया है कि नेस्टेड स्क्रोलिंग इंटरऑप एपीआई का इस्तेमाल BottomSheetDialogFragment के साथ कैसे किया जाता है, ताकि खींचकर छोड़ने की सुविधा को सही तरीके से लागू किया जा सके:
class BottomSheetFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val rootView: View = inflater.inflate(R.layout.fragment_bottom_sheet, container, false)
rootView.findViewById<ComposeView>(R.id.compose_view).apply {
setContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection()
LazyColumn(
Modifier
.nestedScroll(nestedScrollInterop)
.fillMaxSize()
) {
item {
Text(text = "Bottom sheet title")
}
items(10) {
Text(
text = "List item number $it",
modifier = Modifier.fillMaxWidth()
)
}
}
}
return rootView
}
}
}
ध्यान दें कि
rememberNestedScrollInteropConnection()
को जिस एलिमेंट से अटैच किया जाता है उसमें
NestedScrollConnection
इंस्टॉल हो जाएगा. NestedScrollConnection, कंपोज़ लेवल से View लेवल तक डेल्टा ट्रांसमिट करने के लिए ज़िम्मेदार है. इससे एलिमेंट को नेस्टेड स्क्रोलिंग में हिस्सा लेने की अनुमति मिलती है. हालांकि, इससे एलिमेंट अपने-आप स्क्रोल नहीं होते हैं. अपने-आप स्क्रोल न होने वाले कंपोज़ेबल, जैसे कि Box या Column के लिए, ऐसे कॉम्पोनेंट पर स्क्रोल डेल्टा, नेस्ट किए गए स्क्रोल सिस्टम में नहीं फैलेंगे. साथ ही, डेल्टा, rememberNestedScrollInteropConnection() से मिले NestedScrollConnection तक नहीं पहुंचेंगे. इसलिए, वे डेल्टा पैरंट View कॉम्पोनेंट तक नहीं पहुंचेंगे. इस समस्या को हल करने के लिए, पक्का करें कि आपने नेस्ट किए गए इस तरह के कंपोज़ेबल के लिए, स्क्रोल किए जा सकने वाले मॉडिफ़ायर भी सेट किए हों. ज़्यादा जानकारी के लिए, नेस्टेड स्क्रोलिंग के बारे में बताया गया पिछला सेक्शन देखें.
सहयोग न करने वाले माता-पिता View के खाते में मौजूद बच्चा ComposeView
ऐसा व्यू जो ज़रूरी NestedScrolling इंटरफ़ेस को View साइड पर लागू नहीं करता है उसे नॉन-कोऑपरेटिंग व्यू कहा जाता है. ध्यान दें कि इसका मतलब है कि इन Views के साथ नेस्टेड स्क्रोलिंग इंटरऑपरेबिलिटी, बॉक्स से बाहर काम नहीं करती है. सहयोग न करने वाले Views, RecyclerView और ViewPager2 हैं.
अन्य संसाधन
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक टेक्स्ट दिखता है
- जेस्चर के बारे में जानकारी
CoordinatorLayoutसे Compose पर माइग्रेट करना- 'लिखें' विंडो में व्यू इस्तेमाल करना