नेस्ट किए गए स्क्रोलिंग मॉडिफ़ायर

नेस्टेड स्क्रोलिंग एक ऐसा सिस्टम है जिसमें एक-दूसरे में शामिल कई स्क्रोलिंग कॉम्पोनेंट, एक साथ काम करते हैं. इसके लिए, वे स्क्रोल करने के एक जेस्चर पर प्रतिक्रिया देते हैं और स्क्रोलिंग डेल्टा (बदलाव) की जानकारी शेयर करते हैं.

नेस्टेड स्क्रोलिंग सिस्टम की मदद से, स्क्रोल किए जा सकने वाले और क्रम के हिसाब से लिंक किए गए कॉम्पोनेंट के बीच तालमेल बिठाया जा सकता है. ऐसा अक्सर एक ही पैरंट को शेयर करके किया जाता है. यह सिस्टम, स्क्रोलिंग कंटेनर को लिंक करता है. साथ ही, स्क्रोलिंग डेल्टा के साथ इंटरैक्ट करने की अनुमति देता है. ये डेल्टा, के बीच शेयर और आगे बढ़ाए जाते हैं.

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

नेस्टेड स्क्रोलिंग की सुविधा अपने-आप चालू होना

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

Compose के कुछ कॉम्पोनेंट और मॉडिफ़ायर, नेस्टेड स्क्रोलिंग की सुविधा को अपने-आप चालू होने की सुविधा के साथ उपलब्ध कराते हैं. ये कॉम्पोनेंट और मॉडिफ़ायर ये हैं: verticalScroll, horizontalScroll, scrollable, Lazy API, और 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 , स्क्रोल डेल्टा के हिसाब से इमेज के साइज़ में होने वाले बदलाव का हिसाब लगाता है.
  • The currentImageSize स्टेट वैरिएबल, इमेज के मौजूदा साइज़ को सेव करता है. यह साइज़, minImageSize और maxImageSize. imageScale के बीच सीमित होता है. यह currentImageSize से मिलता है.
  • LazyColumn, currentImageSize के हिसाब से ऑफ़सेट होता है.
  • 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 को अनुमति देता है और उसे याद रखता है. यह NestedScrollingParent3 को लागू करने वाले View पैरंट और Compose चाइल्ड के बीच, नेस्टेड स्क्रोलिंग इंटरऑपरेबिलिटी को चालू करता है. इसका इस्तेमाल, 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 स्क्रोलिंग चाइल्ड के लिए पैरंट के तौर पर काम करता है. इसके बाद, Compose पैरंट, नेस्टेड स्क्रोल किए जा सकने वाले चाइल्ड 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 , Compose लेवल से View लेवल तक डेल्टा ट्रांसमिट करने के लिए ज़िम्मेदार है. इससे एलिमेंट, नेस्टेड स्क्रोलिंग में हिस्सा ले पाता है. हालांकि, इससे एलिमेंट की स्क्रोलिंग अपने-आप चालू नहीं होती. ऐसे कंपोज़ेबल जिन्हें अपने-आप स्क्रोल नहीं किया जा सकता, जैसे कि Box या Column, उन पर स्क्रोल डेल्टा, नेस्टेड स्क्रोलिंग सिस्टम में आगे नहीं बढ़ेंगे. साथ ही, डेल्टा, rememberNestedScrollInteropConnection() से मिलने वाले NestedScrollConnection तक नहीं पहुंचेंगे. इसलिए, वे डेल्टा, पैरंट View कॉम्पोनेंट तक नहीं पहुंचेंगे. इस समस्या को हल करने के लिए, पक्का करें कि आपने इस तरह के नेस्टेड कंपोज़ेबल के लिए, स्क्रोल किए जा सकने वाले मॉडिफ़ायर भी सेट किए हों. ज़्यादा जानकारी के लिए, नेस्टेड स्क्रोलिंग पर पिछला सेक्शन देखें.

एक साथ काम न करने वाला पैरंट View, जिसमें चाइल्ड ComposeView शामिल है

एक साथ काम न करने वाला View, वह होता है जो View की ओर से ज़रूरी NestedScrolling इंटरफ़ेस को लागू नहीं करता. ध्यान दें कि इसका मतलब है कि इन Views के साथ नेस्टेड स्क्रोलिंग इंटरऑपरेबिलिटी, डिफ़ॉल्ट रूप से काम नहीं करती. एक साथ काम न करने वाले Views, RecyclerView और ViewPager2 हैं.

अन्य संसाधन