Jetpack Compose में, scrollable2D और draggable2D लो-लेवल मॉडिफ़ायर हैं. इन्हें दो डाइमेंशन में पॉइंटर इनपुट को मैनेज करने के लिए डिज़ाइन किया गया है. स्टैंडर्ड 1D मॉडिफ़ायर scrollable और draggable सिर्फ़ एक ओरिएंटेशन तक सीमित होते हैं. वहीं, 2D वैरिएंट, X और Y, दोनों ऐक्सिस पर एक साथ मूवमेंट को ट्रैक करते हैं.
उदाहरण के लिए, मौजूदा scrollable मॉडिफ़ायर का इस्तेमाल, सिंगल-ओरिएंटेशन में स्क्रोल करने और फ़्लिंग करने के लिए किया जाता है. वहीं, scrollable2d का इस्तेमाल, 2D में स्क्रोल करने और फ़्लिंग करने के लिए किया जाता है. इससे आपको ज़्यादा जटिल लेआउट बनाने में मदद मिलती है, जो सभी दिशाओं में मूव करते हैं. जैसे, स्प्रेडशीट या इमेज व्यूअर. scrollable2d मॉडिफ़ायर,
2D स्थितियों में नेस्टेड स्क्रोलिंग की सुविधा भी देता है.
scrollable2D या draggable2D को चुनें
सही एपीआई चुनना इस बात पर निर्भर करता है कि आपको किन यूज़र इंटरफ़ेस (यूआई) एलिमेंट को मूव करना है और इन एलिमेंट के लिए कौनसा फ़िज़िकल बिहेवियर चाहिए.
Modifier.scrollable2D: कॉन्टेंट को कंटेनर के अंदर ले जाने के लिए, इस मॉडिफ़ायर का इस्तेमाल करें. उदाहरण के लिए, इसका इस्तेमाल मैप, स्प्रेडशीट या फ़ोटो व्यूअर के साथ करें. इनमें कंटेनर के कॉन्टेंट को हॉरिज़ॉन्टल और वर्टिकल, दोनों दिशाओं में स्क्रोल करना पड़ता है. इसमें फ़्लिंग की सुविधा पहले से मौजूद होती है. इससे स्वाइप करने के बाद भी कॉन्टेंट मूव होता रहता है. साथ ही, यह पेज पर मौजूद स्क्रोल करने वाले अन्य कॉम्पोनेंट के साथ भी काम करता है.
Modifier.draggable2D: किसी कॉम्पोनेंट को एक जगह से दूसरी जगह ले जाने के लिए, इस मॉडिफ़ायर का इस्तेमाल करें. यह एक लाइटवेट मॉडिफ़ायर है. इसलिए, उपयोगकर्ता की उंगली रुकते ही मूवमेंट बंद हो जाता है. इसमें फ़्लिंग की सुविधा शामिल नहीं है.
अगर आपको किसी कॉम्पोनेंट को खींचने और छोड़ने लायक बनाना है, लेकिन आपको फ़्लिंग या नेस्ट किए गए स्क्रोल की ज़रूरत नहीं है, तो draggable2D का इस्तेमाल करें.
2D मॉडिफ़ायर लागू करना
यहां दिए गए सेक्शन में, 2D मॉडिफ़ायर इस्तेमाल करने के उदाहरण दिए गए हैं.
Modifier.scrollable2D लागू करें
इस मॉडिफ़ायर का इस्तेमाल उन कंटेनर के लिए करें जिनमें उपयोगकर्ता को कॉन्टेंट को सभी दिशाओं में मूव करना होता है.
2D मूवमेंट डेटा कैप्चर करना
इस उदाहरण में, रॉ 2D मूवमेंट डेटा को कैप्चर करने और X,Y ऑफ़सेट को दिखाने का तरीका बताया गया है:
@Composable private fun Scrollable2DSample() { // 1. Manually track the total distance the user has moved in both X and Y directions var offset by remember { mutableStateOf(Offset.Zero) } Box( modifier = Modifier .fillMaxSize() // ... contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(200.dp) // 2. Attach the 2D scroll logic to capture XY movement deltas .scrollable2D( state = rememberScrollable2DState { delta -> // 3. Update the cumulative offset state with the new movement delta offset += delta // Return the delta to indicate the entire movement was handled by this box delta } ) // ... contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { // 4. Display the current X and Y values from the offset state in real-time Text( text = "X: ${offset.x.roundToInt()}", // ... ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "Y: ${offset.y.roundToInt()}", // ... ) } } } }
ऊपर दिया गया स्निपेट ये काम करता है:
offsetका इस्तेमाल एक ऐसी स्थिति के तौर पर किया जाता है जिसमें उपयोगकर्ता ने कुल कितनी दूरी तक स्क्रोल किया है, इसकी जानकारी होती है.rememberScrollable2DStateमें, उपयोगकर्ता की उंगली से जनरेट होने वाले हर डेल्टा को हैंडल करने के लिए, एक लैम्ब्डा फ़ंक्शन तय किया गया है. कोडoffset.value += delta, मैन्युअल स्थिति को नई पोज़िशन के साथ अपडेट करता है.Textकॉम्पोनेंट, उसoffsetस्टेट की मौजूदा X और Y वैल्यू दिखाते हैं. उपयोगकर्ता के ड्रैग करने पर, ये वैल्यू रीयल-टाइम में अपडेट होती हैं.
बड़े व्यूपोर्ट को पैन करना
इस उदाहरण में, कैप्चर किए गए 2D स्क्रोल किए जा सकने वाले डेटा का इस्तेमाल करने और पैरंट कंटेनर से बड़े कॉन्टेंट पर translationX और translationY लागू करने का तरीका बताया गया है:
@Composable private fun Panning2DImage() { // Manually track the total distance the user has moved in both X and Y directions val offset = remember { mutableStateOf(Offset.Zero) } // Define how gestures are captured. The lambda is called for every finger movement val scrollState = rememberScrollable2DState { delta -> offset.value += delta delta } // The Viewport (Container): A fixed-size box that acts as a window into the larger content Box( modifier = Modifier .size(600.dp, 400.dp) // The visible area dimensions // ... // Hide any parts of the large content that sit outside this container's boundaries .clipToBounds() // Apply the 2D scroll modifier to intercept touch and fling gestures in all directions .scrollable2D(state = scrollState), contentAlignment = Alignment.Center, ) { // The Content: An image given a much larger size than the container viewport Image( painter = painterResource(R.drawable.cheese_5), contentDescription = null, modifier = Modifier .requiredSize(1200.dp, 800.dp) // Manual Scroll Effect: Since scrollable2D doesn't move content automatically, // we use graphicsLayer to shift the drawing position based on the tracked offset. .graphicsLayer { translationX = offset.value.x translationY = offset.value.y }, contentScale = ContentScale.FillBounds ) } }
Modifier.scrollable2D की मदद से बनाया गया, दोनों दिशाओं में पैन करने वाला इमेज व्यूपोर्ट.Modifier.scrollable2D की मदद से बनाया गया, दोनों दिशाओं में पैन करने वाला टेक्स्ट व्यूपोर्ट.पिछले स्निपेट में ये शामिल हैं:
- कंटेनर को तय साइज़ (
600x400dp) पर सेट किया गया है. हालांकि, कॉन्टेंट को बहुत बड़ा साइज़ (1200x800dp) दिया गया है, ताकि उसका साइज़ पैरंट साइज़ के हिसाब से न बदले. - कंटेनर पर
clipToBounds()मॉडिफ़ायर यह पक्का करता है कि600x400बॉक्स के बाहर मौजूद बड़े कॉन्टेंट का कोई भी हिस्सा न दिखे. LazyColumnजैसे हाई-लेवल कॉम्पोनेंट के उलट,scrollable2Dआपके लिए कॉन्टेंट को अपने-आप मूव नहीं करता. इसके बजाय, आपको अपने कॉन्टेंट पर ट्रैक किए गएoffsetको लागू करना होगा. इसके लिए,graphicsLayerट्रांसफ़ॉर्मेशन या लेआउट ऑफ़सेट का इस्तेमाल करें.graphicsLayerब्लॉक में,translationX = offset.value.xऔरtranslationY = offset.value.yआपकी उंगली के मूवमेंट के आधार पर, इमेज या टेक्स्ट की ड्रॉइंग पोज़िशन को शिफ़्ट करते हैं. इससे स्क्रोल करने का विज़ुअल इफ़ेक्ट बनता है.
scrollable2D की मदद से नेस्टेड स्क्रोलिंग लागू करना
इस उदाहरण में बताया गया है कि दो दिशाओं में स्क्रोल होने वाले कॉम्पोनेंट को, एक दिशा में स्क्रोल होने वाले स्टैंडर्ड पैरंट कॉम्पोनेंट में कैसे इंटिग्रेट किया जा सकता है. जैसे, वर्टिकल न्यूज़ फ़ीड.
नेस्टेड स्क्रोलिंग लागू करते समय, इन बातों का ध्यान रखें:
rememberScrollable2DStateके लिए लैम्डा को सिर्फ़ इस्तेमाल किया गया डेल्टा दिखाना चाहिए, ताकि चाइल्ड लिस्ट की सीमा पूरी होने पर, पैरंट लिस्ट अपने-आप काम करना शुरू कर दे.- जब कोई उपयोगकर्ता डायगनल फ़्लिंग करता है, तो 2D वेलोसिटी शेयर की जाती है. अगर ऐनिमेशन के दौरान चाइल्ड व्यू किसी सीमा से टकराता है, तो बची हुई गति को पैरंट व्यू में भेज दिया जाता है, ताकि स्क्रोल करने की प्रोसेस सामान्य तरीके से जारी रहे.
@Composable private fun NestedScrollable2DSample() { var offset by remember { mutableStateOf(Offset.Zero) } val maxScrollDp = 250.dp val maxScrollPx = with(LocalDensity.current) { maxScrollDp.toPx() } Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) .background(Color(0xFFF5F5F5)), horizontalAlignment = Alignment.CenterHorizontally ) { Text( "Scroll down to find the 2D Box", modifier = Modifier.padding(top = 100.dp, bottom = 500.dp), style = TextStyle(fontSize = 18.sp, color = Color.Gray) ) // The Child: A 2D scrollable box with nested scroll coordination Box( modifier = Modifier .size(250.dp) .scrollable2D( state = rememberScrollable2DState { delta -> val oldOffset = offset // Calculate new potential offset and clamp it to our boundaries val newX = (oldOffset.x + delta.x).coerceIn(-maxScrollPx, maxScrollPx) val newY = (oldOffset.y + delta.y).coerceIn(-maxScrollPx, maxScrollPx) val newOffset = Offset(newX, newY) // Calculate exactly how much was consumed by the child val consumed = newOffset - oldOffset offset = newOffset // IMPORTANT: Return ONLY the consumed delta. // The remaining (unconsumed) delta propagates to the parent Column. consumed } ) // ... contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { val density = LocalDensity.current Text("2D Panning Zone", color = Color.White.copy(alpha = 0.7f), fontSize = 12.sp) Spacer(Modifier.height(8.dp)) Text("X: ${with(density) { offset.x.toDp().value.roundToInt() }}dp", color = Color.White, fontWeight = FontWeight.Bold) Text("Y: ${with(density) { offset.y.toDp().value.roundToInt() }}dp", color = Color.White, fontWeight = FontWeight.Bold) } } Text( "Once the Purple Box hits Y: 250 or -250,\nthis parent list will take over the vertical scroll.", textAlign = TextAlign.Center, modifier = Modifier.padding(top = 40.dp, bottom = 800.dp), style = TextStyle(fontSize = 14.sp, color = Color.Gray) ) } }
ऊपर दिए गए स्निपेट में:
- 2D कॉम्पोनेंट, X ऐक्सिस के मूवमेंट का इस्तेमाल करके, अंदरूनी तौर पर पैन कर सकता है. साथ ही, चाइल्ड कॉम्पोनेंट की वर्टिकल बाउंड्री तक पहुंचने के बाद, Y ऐक्सिस के मूवमेंट को पैरंट लिस्ट में भेज सकता है.
- उपयोगकर्ता को 2D सरफ़ेस में ट्रैप करने के बजाय, सिस्टम इस्तेमाल किए गए डेल्टा का हिसाब लगाता है और बाकी बचे डेल्टा को हैरारकी में ऊपर भेजता है. इससे यह पक्का होता है कि उपयोगकर्ता, उंगली उठाए बिना पेज के बाकी हिस्से को स्क्रोल करना जारी रख सकता है.
Modifier.draggable2D लागू करें
Modifier.draggable2D को इस्तेमाल करने के तरीके के बारे में जानने के लिए, Modifier.draggable2D की मदद से दो डाइमेंशन में खींचें और छोड़ें लेख पढ़ें.