स्क्रोल करें

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

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 से मिलता है.
  • 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 से, नेस्ट किए गए स्क्रोल के डेल्टा मिल पाएंगे.

यहां दिए गए उदाहरण में, इस स्थिति में नेस्ट की गई स्क्रोलिंग इंटरऑप को लागू करने का तरीका बताया गया है. साथ ही, इसमें कंपोज़ कोलैप्सिंग टूलबार का इस्तेमाल किया गया है:

@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

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

अन्य संसाधन