स्क्रोल करना

स्क्रोल मॉडिफ़ायर

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 हैं.