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

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

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

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

बदलाव

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

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

ध्यान दें: इस संदर्भ में, "मटेरियल लाइब्रेरी" का मतलब 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+

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

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

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 लाइब्रेरी में ripple API लागू करने का तरीका देखें. साथ ही, अपने डिज़ाइन सिस्टम के लिए, ज़रूरत के हिसाब से 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() के बराबर है. आपको उस फ़ंक्शन को बदलना होगा. इसके बाद, collectLatest लॉजिक को सीधे नोड में लागू करना होगा, न कि Indication में.

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

    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 का इस्तेमाल किया जाएगा.