इंडीकेशन और रिपल एपीआई पर माइग्रेट करें

Modifier.clickable का इस्तेमाल करने वाले इंटरैक्टिव कॉम्पोनेंट की कंपोज़िशन परफ़ॉर्मेंस को बेहतर बनाने के लिए, हमने नए एपीआई लॉन्च किए हैं. इन एपीआई की मदद से, Indication को ज़्यादा बेहतर तरीके से लागू किया जा सकता है. जैसे, रिपल.

androidx.compose.foundation:foundation:1.7.0+ और androidx.compose.material:material-ripple:1.7.0+ में, एपीआई से जुड़े ये बदलाव शामिल हैं:

अब सेवा में नहीं है

बदलाव

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

इसके बजाय, Material लाइब्रेरी में नए ripple() एपीआई उपलब्ध कराए गए हैं.

ध्यान दें: इस संदर्भ में, "मटीरियल लाइब्रेरी" का मतलब androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material, और androidx.wear.compose:compose-material3. से है

RippleTheme

इनमें से कोई एक:

  • Material लाइब्रेरी RippleConfiguration एपीआई का इस्तेमाल करें या
  • अपने डिज़ाइन सिस्टम के लिए रिपल इफ़ेक्ट लागू करना

इस पेज पर, एपीआई के काम करने के तरीके में हुए बदलावों के असर के बारे में बताया गया है. साथ ही, नए एपीआई पर माइग्रेट करने के निर्देश दिए गए हैं.

व्यवहार में बदलाव

लाइब्रेरी के इन वर्शन में, रिपल इफ़ेक्ट के व्यवहार में बदलाव किया गया है:

  • androidx.compose.material:material:1.7.0+
  • androidx.compose.material3:material3:1.3.0+
  • androidx.wear.compose:compose-material:1.4.0+

मटीरियल लाइब्रेरी के ये वर्शन, अब rememberRipple() का इस्तेमाल नहीं करते. इसके बजाय, ये नए रिपल एपीआई का इस्तेमाल करते हैं. इस वजह से, वे LocalRippleTheme से क्वेरी नहीं करते. इसलिए, अगर आपने अपने ऐप्लिकेशन में LocalRippleTheme सेट किया है, तो Material कॉम्पोनेंट इन वैल्यू का इस्तेमाल नहीं करेंगे.

यहां दिए गए सेक्शन में, नए एपीआई पर माइग्रेट करने का तरीका बताया गया है.

rememberRipple से ripple पर माइग्रेट करें

मटीरियल लाइब्रेरी का इस्तेमाल करना

अगर Material लाइब्रेरी का इस्तेमाल किया जा रहा है, तो rememberRipple() को सीधे तौर पर, उससे जुड़ी लाइब्रेरी के ripple() से बदलें. यह एपीआई, Material थीम एपीआई से मिली वैल्यू का इस्तेमाल करके रिपल इफ़ेक्ट बनाता है. इसके बाद, मिले ऑब्जेक्ट को Modifier.clickable और/या अन्य कॉम्पोनेंट को पास करें.

उदाहरण के लिए, इस स्निपेट में ऐसे एपीआई इस्तेमाल किए गए हैं जो अब काम नहीं करते:

Box(
    Modifier.clickable(
        onClick = {},
        interactionSource = remember { MutableInteractionSource() },
        indication = rememberRipple()
    )
) {
    // ...
}

आपको ऊपर दिए गए स्निपेट में यह बदलाव करना चाहिए:

@Composable
private fun RippleExample() {
    Box(
        Modifier.clickable(
            onClick = {},
            interactionSource = remember { MutableInteractionSource() },
            indication = ripple()
        )
    ) {
        // ...
    }
}

ध्यान दें कि ripple() अब ऐप्लिकेशन बनाने की सुविधा नहीं है. इसलिए, इसे याद रखने की ज़रूरत नहीं है. इसे मॉडिफ़ायर की तरह ही, कई कॉम्पोनेंट में फिर से इस्तेमाल किया जा सकता है. इसलिए, रिपल इफ़ेक्ट बनाने की प्रोसेस को टॉप-लेवल वैल्यू में एक्सट्रैक्ट करें, ताकि ऐलोकेशन को सेव किया जा सके.

कस्टम डिज़ाइन सिस्टम लागू करना

अगर आपको अपना डिज़ाइन सिस्टम लागू करना है और आपने रिपल को कॉन्फ़िगर करने के लिए, पहले कस्टम RippleTheme के साथ rememberRipple() का इस्तेमाल किया था, तो आपको अपना रिपल एपीआई उपलब्ध कराना चाहिए. यह एपीआई, material-ripple में एक्सपोज़ किए गए रिपल नोड एपीआई को डेलिगेट करता है. इसके बाद, आपके कॉम्पोनेंट आपके रिपल का इस्तेमाल कर सकते हैं. यह सीधे तौर पर आपकी थीम की वैल्यू का इस्तेमाल करता है. ज़्यादा जानकारी के लिए, RippleTheme से माइग्रेट करना लेख देखें.

RippleTheme से माइग्रेट करें

किसी कॉम्पोनेंट के लिए रिपल इफ़ेक्ट को बंद करने के लिए, RippleTheme का इस्तेमाल करना

material और material3 लाइब्रेरी, RippleConfiguration और LocalRippleConfiguration को एक्सपोज़ करती हैं. इनकी मदद से, सबट्री में रिपल के दिखने के तरीके को कॉन्फ़िगर किया जा सकता है. ध्यान दें कि RippleConfiguration और LocalRippleConfiguration का इस्तेमाल, सिर्फ़ हर कॉम्पोनेंट को पसंद के मुताबिक बनाने के लिए किया जाता है. इन एपीआई की मदद से, ग्लोबल/थीम-वाइड कस्टमाइज़ेशन नहीं किया जा सकता. इस इस्तेमाल के उदाहरण के बारे में ज़्यादा जानने के लिए, किसी ऐप्लिकेशन में सभी रिपल को ग्लोबल तौर पर बदलने के लिए, RippleTheme का इस्तेमाल करना लेख पढ़ें.

उदाहरण के लिए, इस स्निपेट में ऐसे एपीआई इस्तेमाल किए गए हैं जो अब काम नहीं करते:

private object DisabledRippleTheme : RippleTheme {

    @Composable
    override fun defaultColor(): Color = Color.Transparent

    @Composable
    override fun rippleAlpha(): RippleAlpha = RippleAlpha(0f, 0f, 0f, 0f)
}

// ...
    CompositionLocalProvider(LocalRippleTheme provides DisabledRippleTheme) {
        Button {
            // ...
        }
    }

आपको ऊपर दिए गए स्निपेट में यह बदलाव करना चाहिए:

CompositionLocalProvider(LocalRippleConfiguration provides null) {
    Button {
        // ...
    }
}

किसी कॉम्पोनेंट के लिए, रिपल के रंग/ऐल्फ़ा को बदलने के लिए RippleTheme का इस्तेमाल करना

पिछले सेक्शन में बताया गया है कि RippleConfiguration और LocalRippleConfiguration का इस्तेमाल, सिर्फ़ हर कॉम्पोनेंट को पसंद के मुताबिक बनाने के लिए किया जाता है.

उदाहरण के लिए, इस स्निपेट में ऐसे एपीआई इस्तेमाल किए गए हैं जो अब काम नहीं करते:

private object DisabledRippleThemeColorAndAlpha : RippleTheme {

    @Composable
    override fun defaultColor(): Color = Color.Red

    @Composable
    override fun rippleAlpha(): RippleAlpha = MyRippleAlpha
}

// ...
    CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) {
        Button {
            // ...
        }
    }

आपको ऊपर दिए गए स्निपेट में यह बदलाव करना चाहिए:

@OptIn(ExperimentalMaterialApi::class)
private val MyRippleConfiguration =
    RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha)

// ...
    CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) {
        Button {
            // ...
        }
    }

किसी ऐप्लिकेशन में सभी रिपल को ग्लोबल तौर पर बदलने के लिए, RippleTheme का इस्तेमाल करना

पहले, LocalRippleTheme का इस्तेमाल करके, थीम के लेवल पर रिपल के व्यवहार को तय किया जा सकता था. यह मुख्य रूप से, कस्टम डिज़ाइन सिस्टम कंपोज़िशन लोकल और रिपल के बीच इंटिग्रेशन पॉइंट था. material-ripple अब सामान्य थीमिंग प्रिमिटिव को एक्सपोज़ करने के बजाय, createRippleModifierNode() फ़ंक्शन को एक्सपोज़ करता है. इस फ़ंक्शन की मदद से, डिज़ाइन सिस्टम लाइब्रेरी, ज़्यादा ऑर्डर वाले wrapper लागू कर सकती हैं. ये लाइब्रेरी, अपनी थीम की वैल्यू के लिए क्वेरी करती हैं. इसके बाद, रिपल को लागू करने की ज़िम्मेदारी, इस फ़ंक्शन से बनाए गए नोड को सौंपती हैं.

इससे डिज़ाइन सिस्टम को सीधे तौर पर अपनी ज़रूरत के हिसाब से क्वेरी करने की सुविधा मिलती है. साथ ही, उपयोगकर्ता के हिसाब से कॉन्फ़िगर की जा सकने वाली थीम की लेयर को सबसे ऊपर दिखाया जा सकता है. इसके लिए, material-ripple लेयर पर दी गई जानकारी का पालन करने की ज़रूरत नहीं होती. इस बदलाव से यह भी साफ़ तौर पर पता चलता है कि रिपल किस थीम/स्पेसिफ़िकेशन के मुताबिक है. ऐसा इसलिए है, क्योंकि रिपल एपीआई ही उस कॉन्ट्रैक्ट को तय करता है. इसे थीम से नहीं लिया जाता.

ज़्यादा जानकारी के लिए, Material लाइब्रेरी में रिपल एपीआई लागू करने का तरीका देखें. साथ ही, अपने डिज़ाइन सिस्टम के हिसाब से, Material कंपोज़िशन लोकल के कॉल बदलें.

Indication से IndicationNodeFactory पर माइग्रेट करें

Indication के आस-पास से गुज़र रहा है

अगर आपको सिर्फ़ एक Indication बनाना है, जैसे कि Modifier.clickable या Modifier.indication को पास करने के लिए रिपल बनाना, तो आपको कोई बदलाव करने की ज़रूरत नहीं है. IndicationNodeFactory, Indication से इनहेरिट करता है. इसलिए, सब कुछ कंपाइल होता रहेगा और काम करता रहेगा.

Indication बनाया जा रहा है

अगर आपने खुद Indication को लागू किया है, तो ज़्यादातर मामलों में माइग्रेशन आसान होना चाहिए. उदाहरण के लिए, किसी ऐसे Indication पर विचार करें जो दबाने पर स्केल इफ़ेक्ट लागू करता है:

object ScaleIndication : Indication {
    @Composable
    override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
        // key the remember against interactionSource, so if it changes we create a new instance
        val instance = remember(interactionSource) { ScaleIndicationInstance() }

        LaunchedEffect(interactionSource) {
            interactionSource.interactions.collectLatest { interaction ->
                when (interaction) {
                    is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition)
                    is PressInteraction.Release -> instance.animateToResting()
                    is PressInteraction.Cancel -> instance.animateToResting()
                }
            }
        }

        return instance
    }
}

private class ScaleIndicationInstance : IndicationInstance {
    var currentPressPosition: Offset = Offset.Zero
    val animatedScalePercent = Animatable(1f)

    suspend fun animateToPressed(pressPosition: Offset) {
        currentPressPosition = pressPosition
        animatedScalePercent.animateTo(0.9f, spring())
    }

    suspend fun animateToResting() {
        animatedScalePercent.animateTo(1f, spring())
    }

    override fun ContentDrawScope.drawIndication() {
        scale(
            scale = animatedScalePercent.value,
            pivot = currentPressPosition
        ) {
            this@drawIndication.drawContent()
        }
    }
}

इसे दो चरणों में माइग्रेट किया जा सकता है:

  1. ScaleIndicationInstance को DrawModifierNode में माइग्रेट करें. DrawModifierNode के लिए एपीआई का प्लैटफ़ॉर्म, IndicationInstance से काफ़ी मिलता-जुलता है: यह ContentDrawScope#draw() फ़ंक्शन को एक्सपोज़ करता है, जो फ़ंक्शन के हिसाब से IndicationInstance#drawContent() के बराबर है. आपको उस फ़ंक्शन को बदलना होगा. इसके बाद, Indication के बजाय सीधे नोड में collectLatest लॉजिक को लागू करना होगा.

    उदाहरण के लिए, इस स्निपेट में ऐसे एपीआई इस्तेमाल किए गए हैं जो अब काम नहीं करते:

    private class ScaleIndicationInstance : IndicationInstance {
        var currentPressPosition: Offset = Offset.Zero
        val animatedScalePercent = Animatable(1f)
    
        suspend fun animateToPressed(pressPosition: Offset) {
            currentPressPosition = pressPosition
            animatedScalePercent.animateTo(0.9f, spring())
        }
    
        suspend fun animateToResting() {
            animatedScalePercent.animateTo(1f, spring())
        }
    
        override fun ContentDrawScope.drawIndication() {
            scale(
                scale = animatedScalePercent.value,
                pivot = currentPressPosition
            ) {
                this@drawIndication.drawContent()
            }
        }
    }

    आपको ऊपर दिए गए स्निपेट में यह बदलाव करना चाहिए:

    private class ScaleIndicationNode(
        private val interactionSource: InteractionSource
    ) : Modifier.Node(), DrawModifierNode {
        var currentPressPosition: Offset = Offset.Zero
        val animatedScalePercent = Animatable(1f)
    
        private suspend fun animateToPressed(pressPosition: Offset) {
            currentPressPosition = pressPosition
            animatedScalePercent.animateTo(0.9f, spring())
        }
    
        private suspend fun animateToResting() {
            animatedScalePercent.animateTo(1f, spring())
        }
    
        override fun onAttach() {
            coroutineScope.launch {
                interactionSource.interactions.collectLatest { interaction ->
                    when (interaction) {
                        is PressInteraction.Press -> animateToPressed(interaction.pressPosition)
                        is PressInteraction.Release -> animateToResting()
                        is PressInteraction.Cancel -> animateToResting()
                    }
                }
            }
        }
    
        override fun ContentDrawScope.draw() {
            scale(
                scale = animatedScalePercent.value,
                pivot = currentPressPosition
            ) {
                this@draw.drawContent()
            }
        }
    }

  2. IndicationNodeFactory को लागू करने के लिए, ScaleIndication को माइग्रेट करें. कलेक्शन लॉजिक को अब नोड में ले जाया गया है. इसलिए, यह एक बहुत ही आसान फ़ैक्ट्री ऑब्जेक्ट है. इसकी एकमात्र ज़िम्मेदारी, नोड इंस्टेंस बनाना है.

    उदाहरण के लिए, इस स्निपेट में ऐसे एपीआई इस्तेमाल किए गए हैं जो अब काम नहीं करते:

    object ScaleIndication : Indication {
        @Composable
        override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
            // key the remember against interactionSource, so if it changes we create a new instance
            val instance = remember(interactionSource) { ScaleIndicationInstance() }
    
            LaunchedEffect(interactionSource) {
                interactionSource.interactions.collectLatest { interaction ->
                    when (interaction) {
                        is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition)
                        is PressInteraction.Release -> instance.animateToResting()
                        is PressInteraction.Cancel -> instance.animateToResting()
                    }
                }
            }
    
            return instance
        }
    }

    आपको ऊपर दिए गए स्निपेट में यह बदलाव करना चाहिए:

    object ScaleIndicationNodeFactory : IndicationNodeFactory {
        override fun create(interactionSource: InteractionSource): DelegatableNode {
            return ScaleIndicationNode(interactionSource)
        }
    
        override fun hashCode(): Int = -1
    
        override fun equals(other: Any?) = other === this
    }

IndicationInstance बनाने के लिए Indication का इस्तेमाल करना

ज़्यादातर मामलों में, आपको किसी कॉम्पोनेंट के लिए Indication दिखाने के लिए, Modifier.indication का इस्तेमाल करना चाहिए. हालांकि, अगर आपने rememberUpdatedInstance का इस्तेमाल करके, मैन्युअल तरीके से IndicationInstance बनाया है, तो आपको अपने सेटअप को अपडेट करना होगा. इससे यह पता चलेगा कि Indication, IndicationNodeFactory है या नहीं. इससे आपको कम डेटा इस्तेमाल करने वाले सेटअप का इस्तेमाल करने में मदद मिलेगी. उदाहरण के लिए, अगर Modifier.indication एक IndicationNodeFactory है, तो यह बनाए गए नोड को इंटरनल तौर पर डेलिगेट करेगा. अगर ऐसा नहीं है, तो यह rememberUpdatedInstance को कॉल करने के लिए Modifier.composed का इस्तेमाल करेगा.