कंपोज़ेबल को खींचें और छोड़ें

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

Compose के ड्रैग करने वाले एपीआई, रॉ टच इवेंट की जटिलता को कम करते हैं. pointerInput की मदद से, टच के कोऑर्डिनेट की मैन्युअल तरीके से गिनती करने और जेस्चर के लाइफ़साइकल को मैनेज करने के बजाय, Compose के स्टेट मैनेजमेंट और ऐनिमेशन सिस्टम से सीधे तौर पर जुड़े बिल्ट-इन मॉडिफ़ायर का इस्तेमाल किया जा सकता है.

इस पेज पर, ड्रैग करने वाले हर एपीआई का इस्तेमाल करने का तरीका और समय बताया गया है.

ड्रैग करने वाला सही एपीआई चुनना

ड्रैग किए जा सकने वाले कॉन्टेंट को लागू करने के लिए, कई एपीआई का इस्तेमाल किया जा सकता है.

  • ड्रैग किए जा सकने वाले कॉन्टेंट को लागू करने के लिए, स्टैंडर्ड Material कॉम्पोनेंट (जैसे, SwipeToDismissBox या नेविगेशन पैनल) तब चुनें, जब: आपको Material 3 के स्वाइप या ड्रैग करने के डिफ़ॉल्ट तरीके की ज़रूरत हो. उदाहरण के लिए, ईमेल मिटाना, किसी टास्क को संग्रहित करना या साइड मेन्यू / बॉटम शीट खोलना. ये कॉम्पोनेंट, कस्टम स्टेट या लेआउट मैनेजमेंट की ज़रूरत के बिना, बिल्ट-इन तरीके उपलब्ध कराते हैं.
  • Modifier.draggableModifier.draggable तब चुनें, जब: आपको सिर्फ़ रॉ ड्रैग डेल्टा की ज़रूरत हो. उदाहरण के लिए, कस्टम स्लाइडर को ड्रैग करना. ज़्यादा जानकारी के लिए, ड्रैग किए जा सकने वाले कॉम्पोनेंट की मदद से, एक ऐक्सिस पर ड्रैग करना लेख पढ़ें.
    • Choose Modifier.draggable2D when: आपको किसी कॉम्पोनेंट को X और Y, दोनों ऐक्सिस पर एक साथ लगातार मूव करने की ज़रूरत हो. उदाहरण के लिए, मैप पर पैन करना या फ़्लोटिंग ऐक्शन बटन बनाना, जिसे उपयोगकर्ता स्क्रीन पर कहीं भी ड्रैग कर सकता है. ज़्यादा जानकारी के लिए, दो डाइमेंशन में ड्रैग करना लेख पढ़ेंModifier.draggable2D.
  • जब Modifier.anchoredDraggable चुनें: आपको कस्टम इंटरैक्टिव कॉम्पोनेंट बनाने हों, जिन्हें दो या उससे ज़्यादा अलग-अलग पोज़िशन के बीच स्नैप करना हो. उदाहरण के लिए, बॉटम शीट, नेविगेशन पैनल या स्वाइप करके दिखाने की कार्रवाई. ज़्यादा जानकारी के लिए, ऐंकर पॉइंट के बीच कॉम्पोनेंट को ड्रैग करना लेख पढ़ें, जिसमें anchoredDraggable का इस्तेमाल किया गया है.

draggable की मदद से, एक ऐक्सिस पर ड्रैग करना

The draggable मॉडिफ़ायर, एक ओरिएंटेशन में ड्रैग जेस्चर के लिए, हाई-लेवल एंट्री पॉइंट है. साथ ही, यह पिक्सल में ड्रैग की दूरी की रिपोर्ट करता है.

ध्यान दें कि यह मॉडिफ़ायर, scrollable की तरह ही काम करता है. यह सिर्फ़ जेस्चर का पता लगाता है. आपको स्टेट को होल्ड करना होगा और उसे स्क्रीन पर दिखाना होगा . उदाहरण के लिए, offset मॉडिफ़ायर की मदद से एलिमेंट को मूव करना:

@Composable
private fun DraggableText() {
    var offsetX by remember { mutableFloatStateOf(0f) }
    Text(
        modifier = Modifier
            .offset { IntOffset(offsetX.roundToInt(), 0) }
            .draggable(
                orientation = Orientation.Horizontal,
                state = rememberDraggableState { delta ->
                    offsetX += delta
                }
            ),
        text = "Drag me!"
    )
}

अगर आपको ड्रैग करने के पूरे जेस्चर को कंट्रोल करना है, तो ड्रैग जेस्चर डिटेक्टर का इस्तेमाल करें. इसके लिए, pointerInput मॉडिफ़ायर का इस्तेमाल करें.

@Composable
private fun DraggableTextLowLevel() {
    Box(modifier = Modifier.fillMaxSize()) {
        var offsetX by remember { mutableFloatStateOf(0f) }
        var offsetY by remember { mutableFloatStateOf(0f) }

        Box(
            Modifier
                .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
                .background(Color.Blue)
                .size(50.dp)
                .pointerInput(Unit) {
                    detectDragGestures { change, dragAmount ->
                        change.consume()
                        offsetX += dragAmount.x
                        offsetY += dragAmount.y
                    }
                }
        )
    }
}

वैकल्पिक टेक्स्ट यहां डालें
पहला डायग्राम. यूज़र इंटरफ़ेस (यूआई) के किसी एलिमेंट को उंगली से ड्रैग किया जा रहा है

anchoredDraggable की मदद से, ऐंकर पॉइंट के बीच कॉम्पोनेंट को ड्रैग करना

anchoredDraggable मॉडिफ़ायर की मदद से, कॉम्पोनेंट को पहले से तय की गई पोज़िशन के सेट के बीच ड्रैग किया जा सकता है. इन्हें ऐंकर पॉइंट कहा जाता है. जब उपयोगकर्ता ड्रैग जेस्चर छोड़ता है, तो कॉम्पोनेंट सबसे नज़दीकी ऐंकर पर सेट हो जाता है. anchoredDraggable का इस्तेमाल, कस्टम बॉटम शीट, जटिल स्लाइडर या ड्रॉअर पैनल जैसे कॉम्पोनेंट बनाने के लिए किया जाता है.

एपीआई सरफ़ेस

AnchoredDraggable एपीआई के मुख्य हिस्से ये हैं:

  • ऐंकर (DraggableAnchors): यह एक बिल्डर है, जो आपकी कस्टम स्टेट (उदाहरण के लिए, Start, Center, End के लिए enum क्लास) और उनकी पिक्सल ऑफ़सेट वैल्यू के बीच मैपिंग तय करता है.
  • स्टेट (AnchoredDraggableState<T>): यह एक स्टेट होल्डर है, जो कॉम्पोनेंट की मौजूदा पोज़िशन (ऑफ़सेट), वह ऐंकर जिस पर वह सेट है, और उसकी ड्रैग प्रोग्रेस को मैनेज करता है. कॉन्फ़िगरेशन में बदलाव होने पर भी इसकी स्टेट को बनाए रखने के लिए, इसे rememberSaveable और AnchoredDraggableState.Saver<T> का इस्तेमाल करके बनाया जाना चाहिए.
  • जेस्चर मॉडिफ़ायर (Modifier.anchoredDraggable): यह एक मॉडिफ़ायर है, जो Orientation के साथ ड्रैग जेस्चर का पता लगाता है, ड्रैग डेल्टा के साथ स्टेट को अपडेट करता है, और फ़्लिंग जेस्चर को हैंडल करता है, ताकि कॉम्पोनेंट को रिलीज़ करने पर, उसे सबसे नज़दीकी ऐंकर पर ऐनिमेट किया जा सके.

उदाहरण: कस्टम SwipeToDismiss कॉम्पोनेंट बनाना

इस उदाहरण में, कस्टम SwipeToDismiss कॉम्पोनेंट (सफ़ेद बैनर) बनाने का तरीका बताया गया है. उपयोगकर्ता इसे खारिज करने के लिए बाईं ओर स्वाइप कर सकता है या ओवरस्क्रोल स्ट्रेच इफ़ेक्ट देखने के लिए दाईं ओर ड्रैग कर सकता है.

  1. ऐंकर वैल्यू तय करें. एक enum तय करें, जो उन अलग-अलग पोज़िशन को दिखाता है जिनमें ड्रैग किया जा सकने वाला कॉन्टेंट सेट हो सकता है:

    private enum class AnchoredDraggableSwipeDismissValue { DismissedStart, Resting }

  2. इंस्टैंशिएट करें और सेव करें AnchoredDraggableState. ऐक्टिविटी को फिर से बनाने और कॉन्फ़िगरेशन में बदलाव होने पर भी (उदाहरण के लिए, स्क्रीन रोटेशन या थीम में बदलाव), कॉम्पोनेंट की मौजूदा पोज़िशन को बनाए रखने के लिए, AnchoredDraggableState.Saver() के साथ rememberSaveable का इस्तेमाल करें:

    val state =
        rememberSaveable(saver = AnchoredDraggableState.Saver()) {
            AnchoredDraggableState(initialValue = AnchoredDraggableSwipeDismissValue.Resting)
        }

  3. layout, offset, और draggable मॉडिफ़ायर लागू करें. कॉम्पोनेंट को ड्रैग किए जा सकने वाला बनाने और यह पक्का करने के लिए कि उसके ऐंकर पॉइंट, उसके साइज़ के हिसाब से अडजस्ट हों, उस कॉन्टेंट पर ये मॉडिफ़ायर लागू करें जिसे विज़ुअल तौर पर मूव किया जाना चाहिए:

    • Modifier.layout: ऐंकर पोज़िशन अक्सर कॉम्पोनेंट के साइज़ पर निर्भर करती हैं. हालांकि, शुरुआती कंपोज़िशन के दौरान, कॉम्पोनेंट के साइज़ के बारे में पता नहीं होता है. कॉम्पोनेंट को मेज़र करने, उसकी चौड़ाई के आधार पर DraggableAnchors मैप को डाइनैमिक तरीके से कैलकुलेट करने, और state.updateAnchors() का इस्तेमाल करके स्टेट को अपडेट करने के लिए, layout मॉडिफ़ायर का इस्तेमाल करें.

    • Modifier.anchoredDraggable: यह स्टेट से कनेक्ट होता है, तय किए गए ओरिएंटेशन में ड्रैग डेल्टा भेजता है, और फ़्लिंग जेस्चर को हैंडल करता है, ताकि कॉम्पोनेंट को रिलीज़ करने पर, उसे सबसे नज़दीकी ऐंकर पर ऐनिमेट किया जा सके. इस मॉडिफ़ायर को उस कॉम्पोनेंट में जोड़ें जो ड्रैग जेस्चर पर प्रतिक्रिया दे. साथ ही, इसे state और ड्रैग orientation (और चाहें, तो overscrollEffect) पास करें.
    • Modifier.offset: यह ऑफ़सेट लैम्डा के अंदर, state.requireOffset() से ऑफ़सेट को पढ़ता है. इस लैम्डा के अंदर ऑफ़सेट को पढ़ने से यह पक्का होता है कि वैल्यू को सिर्फ़ प्लेसमेंट फ़ेज़ के दौरान पढ़ा जाए. इससे ड्रैग के दौरान, गैर-ज़रूरी रीकंपोज़िशन से बचा जा सकता है. इस मॉडिफ़ायर को उस कॉन्टेंट पर लागू करें जिसे विज़ुअल तौर पर मूव किया जाना चाहिए.

    Box(
        modifier = Modifier
            .fillMaxWidth()
            // Read state.requireOffset() inside the offset lambda to defer reading to the placement phase
            .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints)
                val anchors = DraggableAnchors {
                    AnchoredDraggableSwipeDismissValue.DismissedStart at -placeable.width.toFloat()
                    AnchoredDraggableSwipeDismissValue.Resting at 0f
                }
                state.updateAnchors(anchors)
                layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
            }
            // Pass overscrollEffect to anchoredDraggable so deltas beyond the bounds (min and max anchor) are dispatched to overscrollEffect, and render the visual effect with Modifier.overscroll.
            .overscroll(overscrollEffect)
            .anchoredDraggable(
                state,
                Orientation.Horizontal,
                overscrollEffect = overscrollEffect
            )
    ) {
        dismissContent()
    }

  4. [ज़रूरी नहीं] ओवरस्क्रोल को इंटिग्रेट करें. overscrollEffect एपीआई एक विज़ुअल इफ़ेक्ट लागू करता है. यह इफ़ेक्ट तब दिखता है, जब स्क्रोल या फ़्लिंग की मदद से, स्क्रोल करने वाले कंटेनर के किनारे पहुंच जाते हैं. जब कोई उपयोगकर्ता, ओवरस्क्रोल इफ़ेक्ट (आम तौर पर, स्ट्रेच या ग्लो) की मदद से, तय की गई ऐंकर बाउंड्री से आगे ड्रैग करता है, तो विज़ुअल फ़ीडबैक दिया जा सकता है.

    1. डिस्पैच करें: overscrollEffect इंस्टेंस को anchoredDraggable मॉडिफ़ायर पर पास करें. इससे ड्रैग डेल्टा, AnchoredDraggable स्टेट या ओवरस्क्रोल इफ़ेक्ट पर डिस्पैच होते हैं. यह इस बात पर निर्भर करता है कि जेस्चर, ऐंकर बाउंड्री के अंदर है या बाहर.
    2. रेंडर करें: Modifier.overscroll को उसी OverscrollEffect इंस्टेंस के साथ उस कॉम्पोनेंट पर लागू करें जिसे विज़ुअल तौर पर इफ़ेक्ट रेंडर करना चाहिए.

यहां दिए गए स्निपेट में, स्टैंडर्ड स्ट्रेच इफ़ेक्ट जोड़ने का तरीका बताया गया है:

val overscrollEffect = rememberOverscrollEffect()
Box(
    modifier.fillMaxWidth()
) {
    Box(Modifier.fillMaxWidth()) {
        background()
    }
    Box(
        modifier = Modifier
            .fillMaxWidth()
            // Read state.requireOffset() inside the offset lambda to defer reading to the placement phase
            .offset { IntOffset(state.requireOffset().roundToInt(), 0) }
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints)
                val anchors = DraggableAnchors {
                    AnchoredDraggableSwipeDismissValue.DismissedStart at -placeable.width.toFloat()
                    AnchoredDraggableSwipeDismissValue.Resting at 0f
                }
                state.updateAnchors(anchors)
                layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
            }
            // Pass overscrollEffect to anchoredDraggable so deltas beyond the bounds (min and max anchor) are dispatched to overscrollEffect, and render the visual effect with Modifier.overscroll.
            .overscroll(overscrollEffect)
            .anchoredDraggable(
                state,
                Orientation.Horizontal,
                overscrollEffect = overscrollEffect
            )
    ) {
        dismissContent()
    }
}

नतीजा

ऊपर दिए गए कोड स्निपेट से, एक सफ़ेद बैनर बनता है. इसमें "खारिज करने के लिए मुझे ड्रैग करें!" टेक्स्ट दिखता है. उपयोगकर्ता इसे खारिज करने के लिए बाईं ओर स्वाइप कर सकता है या ओवरस्क्रोल स्ट्रेच इफ़ेक्ट देखने के लिए दाईं ओर ड्रैग कर सकता है:

वीडियो 2. स्वाइप करके खारिज करने वाले कस्टम कॉम्पोनेंट को लागू करने का तरीका.

उदाहरण: जेस्चर से कंट्रोल होने वाला कस्टम नेविगेशन पैनल बनाना

इस उदाहरण में, जेस्चर से कंट्रोल होने वाला कस्टम नेविगेशन ड्रॉअर बनाने का तरीका बताया गया है. जब उपयोगकर्ता हॉरिज़ॉन्टली ड्रैग करता है, तो फ़ोरग्राउंड स्क्रीन (ScreenContents) दाईं ओर स्लाइड होती है. साथ ही, 1.0f से 0.8f तक स्केल डाउन होती है. इसके अलावा, इसके कोने राउंड होते हैं, ताकि इसके नीचे मौजूद नेविगेशन पैनल दिखे.

  1. ऐंकर स्टेट और पोज़िशन तय करें. इस उदाहरण में, ऐंकर, ड्रॉअर की खुली और बंद स्टेट हैं. Open और Closed स्टेट के लिए enum तय करें. साथ ही, DraggableAnchors का इस्तेमाल करके, उन्हें पिक्सल ऑफ़सेट पर मैप करें:

    enum class DrawerState {
        Open,
        Closed
    }
    
        val anchors = DraggableAnchors {
            DrawerState.Open at drawerWidth
            DrawerState.Closed at 0f
        }
        val state = remember {
            AnchoredDraggableState(
                initialValue = DrawerState.Closed,
                anchors = anchors,
            )
        }

  2. किसी आइटम पर क्लिक करने पर, anchoredDraggableState को ऐनिमेट करें. Box के सबसे नीचे HomeScreenDrawer और सबसे ऊपर ScreenContents को रखें. ड्रॉअर स्थिर रहता है, जबकि सबसे ऊपर की लेयर, उसके ऊपर ट्रांसलेट और स्केल होती है.

    जब कोई उपयोगकर्ता ड्रॉअर का कोई आइटम चुनता है, तो ऐक्टिव स्क्रीन स्टेट को अपडेट करें और state.animateTo(DrawerState.Closed) का इस्तेमाल करके, ड्रॉअर को अपने-आप खारिज करें:

    @Composable
    private fun AnchoredDraggableDrawerOnClickSample(
        modifier: Modifier = Modifier,
        state: AnchoredDraggableState<DrawerState>,
        coroutineScope: CoroutineScope
    ) {
        var screenState by remember { mutableStateOf(ScreenState.Home) }
    
        Box(modifier = modifier.fillMaxSize()) {
            HomeScreenDrawer(
                selectedScreen = screenState,
                onScreenSelected = { screen ->
                    screenState = screen
                    // Dismiss drawer when a destination is selected
                    coroutineScope.launch { state.animateTo(DrawerState.Closed) }
                }
            )
    
            // ScreenContents(...)
        }
    }

  3. कई विज़ुअल प्रॉपर्टी में बदलाव करने के लिए, state.requireOffset() का इस्तेमाल करें. सिर्फ़ कॉन्टेंट को ऑफ़सेट करने के बजाय, कई प्रॉपर्टी में बदलाव किया जा सकता है Modifier.graphicsLayer की मदद से. इससे ड्रैग प्रोग्रेस के आधार पर, ट्रांसलेशन, स्केल, और कॉर्नर रेडियस को एक साथ ऐनिमेट किया जा सकता है:

    • मौजूदा ड्रैग ऑफ़सेट पाने के लिए, state.requireOffset() का इस्तेमाल करें.
    • ऑफ़सेट और ड्रॉअर की चौड़ाई के हिसाब से, प्रपोर्शनल स्केल (1f से 0.8f) और कॉर्नर रेडियस (0.dp से 32.dp) कैलकुलेट करने के लिए, lerp() (लीनियर इंटरपोलेशन) का इस्तेमाल करें.
    • Modifier.graphicsLayer , ड्रॉ फ़ेज़ में बदलाव करता है. इससे ड्रैग के दौरान, रीकंपोज़िशन ट्रिगर किए बिना, हाई-परफ़ॉर्मेंस ऐनिमेशन पक्का होते हैं.

    @Composable
    private fun AnchoredDraggableDrawerGraphicsLayerSample(
        state: AnchoredDraggableState<DrawerState>,
        drawerWidth: Float,
        selectedScreen: ScreenState,
        onDrawerClicked: () -> Unit
    ) {
        ScreenContents(
            selectedScreen = selectedScreen,
            onDrawerClicked = onDrawerClicked,
            modifier = Modifier
                .graphicsLayer {
                    this.translationX = state.requireOffset()
                    val scale = lerp(1f, 0.8f, state.requireOffset() / drawerWidth)
                    this.scaleX = scale
                    this.scaleY = scale
                    val cornerRadius = lerp(0f, 32f, state.requireOffset() / drawerWidth)
                    this.shape = RoundedCornerShape(cornerRadius.dp)
                    this.clip = true
                }
                .anchoredDraggable(state, Orientation.Horizontal)
        )
    }

  4. प्रोग्राम के हिसाब से होने वाले ऐनिमेशन ट्रिगर करें animateTo. कोरोटीन स्कोप में state.animateTo() लॉन्च करके, ड्रॉअर को प्रोग्राम के हिसाब से कंट्रोल करें. उदाहरण के लिए, मेन्यू आइकॉन पर टैप करने या ड्रॉअर का कोई आइटम चुनने पर:

    fun toggleDrawerState(
        state: AnchoredDraggableState<DrawerState>,
        coroutineScope: CoroutineScope
    ) {
        coroutineScope.launch {
            if (state.currentValue == DrawerState.Open) {
                state.animateTo(DrawerState.Closed)
            } else {
                state.animateTo(DrawerState.Open)
            }
        }
    }

नतीजा

वीडियो 3. फ़ोरग्राउंड स्क्रीन (स्लीप) को दाईं ओर ड्रैग किया जाता है. इससे इसके नीचे मौजूद नेविगेशन ड्रॉअर दिखता है.

Modifier.draggable2D की मदद से, दो डाइमेंशन में ड्रैग करना

यूज़र इंटरफ़ेस (यूआई) के अलग-अलग एलिमेंट को X और Y, दोनों ऐक्सिस पर एक साथ मूव करने के लिए, draggable2D मॉडिफ़ायर का इस्तेमाल करें.

कंपोज़ेबल एलिमेंट को ड्रैग करना

इस उदाहरण में, draggable2D के सबसे आम इस्तेमाल के बारे में बताया गया है. इससे उपयोगकर्ता, यूज़र इंटरफ़ेस (यूआई) के किसी एलिमेंट को चुनकर, उसे पैरंट कंटेनर में कहीं भी रीपोज़िशन कर सकता है.

@Composable
private fun DraggableComposableElement() {
    // 1. Track the position of the floating window
    var offset by remember { mutableStateOf(Offset.Zero) }

    Box(modifier = Modifier.fillMaxSize().background(Color(0xFFF5F5F5))) {
        Box(
            modifier = Modifier
                // 2. Apply the offset to the box's position
                .offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
                // ...
                // 3. Attach the 2D drag logic
                .draggable2D(
                    state = rememberDraggable2DState { delta ->
                        // 4. Update the position based on the movement delta
                        offset += delta
                    }
                ),
            contentAlignment = Alignment.Center
        ) {
            Text("Video Preview", color = Color.White, fontSize = 12.sp)
        }
    }
}

वीडियो 4. ग्रे बैकग्राउंड पर, बैंगनी रंग के छोटे बॉक्स को रीपोज़िशन किया जा रहा है. इससे डायरेक्ट 2D ड्रैग करने का तरीका दिखता है. इसमें, उपयोगकर्ता की उंगली हटाने पर, एलिमेंट तुरंत मूव करना बंद कर देता है.

ऊपर दिए गए कोड स्निपेट में ये चीज़ें शामिल हैं:

  • यह offset स्टेट का इस्तेमाल करके, बॉक्स की पोज़िशन को ट्रैक करता है.
  • यह ड्रैग डेल्टा के आधार पर, कॉम्पोनेंट की पोज़िशन को शिफ़्ट करने के लिए, offset मॉडिफ़ायर का इस्तेमाल करता है.
  • फ़्लिंग की सुविधा उपलब्ध न होने की वजह से, उपयोगकर्ता की उंगली हटाने पर, बॉक्स तुरंत मूव करना बंद कर देता है.

पैरंट के ड्रैग एरिया के आधार पर, चाइल्ड कंपोज़ेबल को ड्रैग करना

इस उदाहरण में, draggable2D का इस्तेमाल करके, 2D इनपुट एरिया बनाने का तरीका बताया गया है. इसमें, सिलेक्टर नॉब को किसी खास सरफ़ेस के अंदर सीमित किया जाता है. ड्रैग किए जा सकने वाले एलिमेंट के उदाहरण के उलट, जिसमें कॉम्पोनेंट खुद मूव होता है, इस उदाहरण में 2D डेल्टा का इस्तेमाल करके, चाइल्ड कंपोज़ेबल "सिलेक्टर" को कलर पिकर पर मूव किया जाता है:

@Composable
private fun ExampleColorSelector(
    // ...
)  {
    // 1. Maintain the 2D position of the selector in state.
    var selectorOffset by remember { mutableStateOf(Offset.Zero) }

    // 2. Track the size of the background container.
    var containerSize by remember { mutableStateOf(IntSize.Zero) }

    Box(
        modifier = Modifier
            .size(300.dp, 200.dp)
            // Capture the actual pixel dimensions of the container when it's laid out.
            .onSizeChanged { containerSize = it }
            .clip(RoundedCornerShape(12.dp))
            .background(
                brush = remember(hue) {
                    // Create a simple gradient representing Saturation and Value for the given Hue.
                    Brush.linearGradient(listOf(Color.White, Color.hsv(hue, 1f, 1f)))
                }
            )
    ) {
        Box(
            modifier = Modifier
                .size(24.dp)
                .graphicsLayer {
                    // Center the selector on the finger by subtracting half its size.
                    translationX = selectorOffset.x - (24.dp.toPx() / 2)
                    translationY = selectorOffset.y - (24.dp.toPx() / 2)
                }
                // ...
                // 3. Configure 2D touch dragging.
                .draggable2D(
                    state = rememberDraggable2DState { delta ->
                        // 4. Calculate the new position and clamp it to the container bounds
                        val newX = (selectorOffset.x + delta.x)
                            .coerceIn(0f, containerSize.width.toFloat())
                        val newY = (selectorOffset.y + delta.y)
                            .coerceIn(0f, containerSize.height.toFloat())

                        selectorOffset = Offset(newX, newY)
                    }
                )
        )
    }
}

वीडियो 5. कलर ग्रेडिएंट में, सफ़ेद रंग का गोलाकार सिलेक्टर नॉब है. इसे किसी भी दिशा में ड्रैग किया जा सकता है. इससे यह दिखता है कि चुने गए रंग की वैल्यू को अपडेट करने के लिए, 2D डेल्टा को कंटेनर की बाउंड्री पर कैसे क्लैंप किया जाता है.

ऊपर दिए गए स्निपेट में ये चीज़ें शामिल हैं:

  • यह ग्रेडिएंट कंटेनर के असल डाइमेंशन कैप्चर करने के लिए, onSizeChanged मॉडिफ़ायर का इस्तेमाल करता है. सिलेक्टर को यह पता होता है कि किनारे कहां हैं.
  • graphicsLayer के अंदर, यह translationX और translationY को अडजस्ट करता है, ताकि ड्रैग करते समय, सिलेक्टर बीच में रहे.