स्क्रोल मॉडिफ़ायर
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
बनाते समय, आपको एक consumeScrollDelta
फ़ंक्शन देना होगा. इसे हर स्क्रोल चरण पर (जेस्चर इनपुट, स्मूद स्क्रोलिंग या फ़्लिंग करके) पिक्सल में डेल्टा के साथ शुरू किया जाएगा. इस फ़ंक्शन से, स्क्रोल की गई दूरी का पता चलना चाहिए. इससे यह पक्का किया जा सकेगा कि scrollable
मॉडिफ़ायर वाले नेस्ट किए गए एलिमेंट के मामलों में, इवेंट सही तरीके से प्रोपैगेट हो.
नीचे दिया गया स्निपेट, जेस्चर का पता लगाता है और ऑफ़सेट के लिए संख्या वाली वैल्यू दिखाता है. हालांकि, यह किसी भी एलिमेंट को ऑफ़सेट नहीं करता:
@Composable private fun ScrollableSample() { // actual composable state var offset by remember { mutableStateOf(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
से मिलता है.currentImageSize
के आधार परLazyColumn
ऑफ़सेट.Image
, कैलकुलेट किया गया स्केल लागू करने के लिए,graphicsLayer
मॉडिफ़ायर का इस्तेमाल करता है.graphicsLayer
में मौजूदtranslationY
से यह पक्का होता है कि इमेज का आकार बदलने पर, वह वर्टिकल तौर पर बीच में बनी रहे.
नतीजा
पिछले स्निपेट से, स्क्रोल करने पर इमेज का स्केलिंग इफ़ेक्ट दिखता है:
नेस्ट किया गया स्क्रोलिंग इंटरऑपरेबिलिटी
स्क्रोल किए जा सकने वाले कॉम्पोज़ेबल में, स्क्रोल किए जा सकने वाले View
एलिमेंट को नेस्ट करने या इसके उलट करने पर, आपको समस्याएं आ सकती हैं. आम तौर पर, सबसे ज़्यादा गड़बड़ियां तब होती हैं, जब आप चाइल्ड एलिमेंट को स्क्रोल करके उसके शुरू या आखिर के बाउंड तक पहुंच जाते हैं और पैरंट एलिमेंट को स्क्रोल करने के लिए छोड़ देते हैं. हालांकि, ऐसा हो सकता है कि यह उम्मीद के मुताबिक न हो या उम्मीद के मुताबिक काम न करे.
यह समस्या, स्क्रोल किए जा सकने वाले कॉम्पोनेंट में बनी उम्मीदों की वजह से होती है.
स्क्रोल किए जा सकने वाले कॉम्पोज़ेबल में "डिफ़ॉल्ट रूप से नेस्ट किया गया स्क्रोल" नियम होता है. इसका मतलब है कि स्क्रोल किए जा सकने वाले किसी भी कंटेनर को नेस्ट किए गए स्क्रोल चेन में शामिल होना चाहिए. यह NestedScrollConnection
के ज़रिए पैरंट के तौर पर और NestedScrollDispatcher
के ज़रिए चाइल्ड के तौर पर, दोनों तरह से शामिल होना चाहिए.
जब बच्चा बाउंड में होगा, तब वह माता-पिता के लिए नेस्ट किया गया स्क्रोल चलाएगा. उदाहरण के लिए, इस नियम की मदद से, Compose Pager
और Compose LazyRow
एक साथ बेहतर तरीके से काम करते हैं. हालांकि, ViewPager2
या RecyclerView
का इस्तेमाल करके इंटरऑपरेबिलिटी स्क्रोलिंग करने पर, चाइल्ड से पैरंट में लगातार स्क्रोल नहीं किया जा सकता. ऐसा इसलिए, क्योंकि इनमें NestedScrollingParent3
लागू नहीं होता.
नेस्ट किए गए स्क्रोल किए जा सकने वाले View
एलिमेंट और स्क्रोल किए जा सकने वाले कॉम्पोज़ेबल के बीच नेस्ट किए गए स्क्रोलिंग इंटरऑप एपीआई को चालू करने के लिए, नेस्ट किए गए स्क्रोलिंग इंटरऑप एपीआई का इस्तेमाल किया जा सकता है. इससे, इन समस्याओं को कम करने में मदद मिलती है.
सहयोग करने वाला माता-पिता View
, जिसमें एक बच्चा ComposeView
है
साथ मिलकर काम करने वाला पैरंट View
वह होता है जो पहले से ही NestedScrollingParent3
को लागू करता है. इसलिए, वह साथ मिलकर काम करने वाले नेस्ट किए गए चाइल्ड कॉम्पोज़ेबल से स्क्रोलिंग डेल्टा पा सकता है. इस मामले में ComposeView
, बच्चे के तौर पर काम करेगा और उसे NestedScrollingChild3
को (अप्रत्यक्ष रूप से) लागू करना होगा.
सहयोग करने वाले माता-पिता का एक उदाहरण androidx.coordinatorlayout.widget.CoordinatorLayout
है.
अगर आपको स्क्रोल किए जा सकने वाले View
पैरंट कंटेनर और स्क्रोल किए जा सकने वाले नेस्ट किए गए चाइल्ड कॉम्पोज़ेबल के बीच नेस्ट किया गया स्क्रोलिंग इंटरऑपरेबिलिटी चाहिए, तो rememberNestedScrollInteropConnection()
का इस्तेमाल किया जा सकता है.
rememberNestedScrollInteropConnection()
, 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
से नेस्ट किए गए स्क्रोल डेल्टा मिल पाएंगे.
नीचे दिए गए उदाहरण में बताया गया है कि इस स्थिति में, नेस्ट किए गए स्क्रोलिंग इंटरऑपरेबिलिटी के साथ-साथ, 'लिखें' टूलबार को छोटा करने की सुविधा कैसे चालू की जा सकती है:
@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
, डेल्टा को Compose लेवल से View
लेवल पर भेजने के लिए ज़िम्मेदार है. इससे एलिमेंट, नेस्ट किए गए स्क्रोलिंग में हिस्सा ले पाता है. हालांकि, इससे एलिमेंट अपने-आप स्क्रोल नहीं होते. Box
या Column
जैसे ऐसे कॉम्पोनेंट जो अपने-आप स्क्रोल नहीं होते, उनके लिए स्क्रोल डेल्टा, नेस्ट किए गए स्क्रोल सिस्टम में प्रोपेगेट नहीं होंगे. साथ ही, डेल्टा rememberNestedScrollInteropConnection()
से मिले NestedScrollConnection
तक नहीं पहुंचेंगे. इसलिए, वे डेल्टा पैरंट View
कॉम्पोनेंट तक नहीं पहुंचेंगे. इस समस्या को हल करने के लिए,
पक्का करें कि आपने नेस्ट किए गए इस तरह के कॉम्पोज़ेबल के लिए भी, स्क्रोल किए जा सकने वाले मॉडिफ़ायर सेट किए हों. ज़्यादा जानकारी के लिए, नेस्ट किए गए स्क्रोलिंग सेक्शन देखें.
ऐसा माता-पिता View
जो सहयोग नहीं कर रहा है और जिसके पास बच्चा ComposeView
है
ऐसा व्यू जो View
साइड पर ज़रूरी NestedScrolling
इंटरफ़ेस लागू नहीं करता है. ध्यान दें कि इसका मतलब है कि इन Views
के साथ नेस्ट किया गया स्क्रोलिंग इंटरऑपरेबिलिटी, बॉक्स के बाहर काम नहीं करता. सहयोग न करने वाले Views
, RecyclerView
और ViewPager2
हैं.
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक टेक्स्ट दिखता है
- जेस्चर के बारे में जानकारी
CoordinatorLayout
को 'लिखें' पर माइग्रेट करना- 'लिखें' सुविधा में व्यू का इस्तेमाल करना