कस्टम मॉडिफ़ायर बनाएं

Compose में, आम तौर पर इस्तेमाल होने वाले फ़ंक्शन के लिए कई मोडिफ़ायर पहले से मौजूद होते हैं. हालांकि, आपके पास अपने हिसाब से मोडिफ़ायर बनाने का विकल्प भी होता है.

मॉडिफ़ायर के कई हिस्से होते हैं:

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

ज़रूरत के हिसाब से, कस्टम मॉडिफ़ायर को लागू करने के कई तरीके हैं. आम तौर पर, कस्टम मॉडिफ़ायर लागू करने का सबसे आसान तरीका, सिर्फ़ कस्टम मॉडिफ़ायर फ़ैक्ट्री लागू करना है. यह फ़ैक्ट्री, पहले से तय की गई अन्य मॉडिफ़ायर फ़ैक्ट्री को एक साथ जोड़ती है. अगर आपको ज़्यादा कस्टम व्यवहार की ज़रूरत है, तो Modifier.Node एपीआई का इस्तेमाल करके, बदलाव करने वाले एलिमेंट को लागू करें. ये एपीआई, लेवल में कम होते हैं, लेकिन ज़्यादा सुविधाएं देते हैं.

मौजूदा मॉडिफ़ायर को एक साथ जोड़ना

अक्सर, सिर्फ़ मौजूदा मॉडिफ़ायर का इस्तेमाल करके कस्टम मॉडिफ़ायर बनाए जा सकते हैं. उदाहरण के लिए, Modifier.clip() को graphicsLayer मॉडिफ़ायर का इस्तेमाल करके लागू किया जाता है. इस रणनीति में, पहले से मौजूद मॉडिफ़ायर एलिमेंट का इस्तेमाल किया जाता है. साथ ही, आपको अपनी कस्टम मॉडिफ़ायर फ़ैक्ट्री भी देनी होती है.

अपना कस्टम मॉडिफ़ायर लागू करने से पहले, देखें कि क्या उसी रणनीति का इस्तेमाल किया जा सकता है.

fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)

इसके अलावा, अगर आपको लगता है कि आपने एक ही ग्रुप के कई मॉडिफ़ायर बार-बार इस्तेमाल किए हैं, तो उन्हें अपने मॉडिफ़ायर में शामिल किया जा सकता है:

fun Modifier.myBackground(color: Color) = padding(16.dp)
    .clip(RoundedCornerShape(8.dp))
    .background(color)

कंपोजेबल मॉडिफ़ायर फ़ैक्ट्री का इस्तेमाल करके कस्टम मॉडिफ़ायर बनाना

किसी मौजूदा मॉडिफ़ायर को वैल्यू पास करने के लिए, कंपोजेबल फ़ंक्शन का इस्तेमाल करके कस्टम मॉडिफ़ायर भी बनाया जा सकता है. इसे कंपोजेबल मॉडिफ़ायर फ़ैक्ट्री कहा जाता है.

मॉडिफ़ायर बनाने के लिए, कंपोज़ेबल मॉडिफ़ायर फ़ैक्ट्री का इस्तेमाल करने पर, animate*AsState और Compose के साथ काम करने वाले अन्य ऐनिमेशन एपीआई जैसे, बेहतर लेवल के Compose एपीआई का इस्तेमाल भी किया जा सकता है. उदाहरण के लिए, नीचे दिया गया स्निपेट एक ऐसा मॉडिफ़ायर दिखाता है जो चालू/बंद होने पर ऐल्फ़ा में बदलाव को ऐनिमेट करता है:

@Composable
fun Modifier.fade(enable: Boolean): Modifier {
    val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f)
    return this then Modifier.graphicsLayer { this.alpha = alpha }
}

अगर आपका कस्टम मॉडिफ़ायर, CompositionLocal से डिफ़ॉल्ट वैल्यू देने का आसान तरीका है, तो इसे लागू करने का सबसे आसान तरीका, कंपोज़ेबल मॉडिफ़ायर फ़ैक्ट्री का इस्तेमाल करना है:

@Composable
fun Modifier.fadedBackground(): Modifier {
    val color = LocalContentColor.current
    return this then Modifier.background(color.copy(alpha = 0.5f))
}

इस तरीके में कुछ सावधानियां बरतनी पड़ती हैं. इनके बारे में नीचे बताया गया है.

CompositionLocal वैल्यू, मॉडिफ़ायर फ़ैक्ट्री की कॉल साइट पर हल की जाती हैं

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

@Composable
fun Modifier.myBackground(): Modifier {
    val color = LocalContentColor.current
    return this then Modifier.background(color.copy(alpha = 0.5f))
}

@Composable
fun MyScreen() {
    CompositionLocalProvider(LocalContentColor provides Color.Green) {
        // Background modifier created with green background
        val backgroundModifier = Modifier.myBackground()

        // LocalContentColor updated to red
        CompositionLocalProvider(LocalContentColor provides Color.Red) {

            // Box will have green background, not red as expected.
            Box(modifier = backgroundModifier)
        }
    }
}

अगर आपको लगता है कि आपका मॉडिफ़ायर इस तरह काम नहीं करेगा, तो इसके बजाय कस्टम Modifier.Node का इस्तेमाल करें. ऐसा इसलिए, क्योंकि कॉम्पोज़िशन लोकल, इस्तेमाल की जगह पर सही तरीके से हल हो जाएंगे और उन्हें सुरक्षित तरीके से होस्ट किया जा सकेगा.

Composable फ़ंक्शन के मॉडिफ़ायर कभी भी स्किप नहीं किए जाते

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

कॉम्पोज़ेबल फ़ंक्शन में ही, कॉम्पोज़ेबल फ़ंक्शन के मॉडिफ़ायर को कॉल किया जाना चाहिए

सभी कॉम्पोज़ेबल फ़ंक्शन की तरह, कॉम्पोज़ेबल फ़ैक्ट्री मॉडिफ़ायर को कंपोज़िशन में से ही कॉल किया जाना चाहिए. इससे यह तय होता है कि किसी मॉडिफ़ायर को कहां होस्ट किया जा सकता है, क्योंकि इसे कभी भी कॉम्पोज़िशन से बाहर नहीं होस्ट किया जा सकता. इसके मुकाबले, नॉन-कंपोज़ेबल मॉडिफ़ायर फ़ैक्ट्री को, आसानी से फिर से इस्तेमाल करने और परफ़ॉर्मेंस को बेहतर बनाने के लिए, कंपोज़ेबल फ़ंक्शन से बाहर रखा जा सकता है:

val extractedModifier = Modifier.background(Color.Red) // Hoisted to save allocations

@Composable
fun Modifier.composableModifier(): Modifier {
    val color = LocalContentColor.current.copy(alpha = 0.5f)
    return this then Modifier.background(color)
}

@Composable
fun MyComposable() {
    val composedModifier = Modifier.composableModifier() // Cannot be extracted any higher
}

Modifier.Node का इस्तेमाल करके, कस्टम मॉडिफ़ायर का व्यवहार लागू करना

Modifier.Node, Compose में मॉडिफ़ायर बनाने के लिए, लोअर लेवल का एपीआई है. यह वही एपीआई है जिसमें Compose अपने मॉडिफ़ायर लागू करता है. साथ ही, यह कस्टम मॉडिफ़ायर बनाने का सबसे बेहतर तरीका है.

Modifier.Node का इस्तेमाल करके कस्टम मॉडिफ़ायर लागू करना

Modifier.Node का इस्तेमाल करके कस्टम मॉडिफ़ायर लागू करने के तीन चरण हैं:

  • Modifier.Node लागू करने का ऐसा तरीका जिसमें आपके मॉडिफ़ायर का लॉजिक और स्थिति मौजूद होती है.
  • ऐसा ModifierNodeElement जो मॉडिफ़ायर नोड इंस्टेंस बनाता है और उन्हें अपडेट करता है.
  • ऊपर बताई गई वैकल्पिक मॉडिफ़ायर फ़ैक्ट्री.

ModifierNodeElement क्लास स्टेटलेस होती हैं और हर रीकंपोज़िशन के लिए नए इंस्टेंस असाइन किए जाते हैं. वहीं, Modifier.Node क्लास स्टेटफ़ुल हो सकती हैं और कई रीकंपोज़िशन के दौरान भी बनी रहेंगी. साथ ही, इनका फिर से इस्तेमाल भी किया जा सकता है.

नीचे दिए गए सेक्शन में, हर हिस्से के बारे में बताया गया है. साथ ही, सर्कल बनाने के लिए कस्टम मॉडिफ़ायर बनाने का उदाहरण भी दिया गया है.

Modifier.Node

Modifier.Node लागू करने (इस उदाहरण में, CircleNode) से आपके कस्टम मॉडिफ़ायर की सुविधा लागू होती है.

// Modifier.Node
private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() {
    override fun ContentDrawScope.draw() {
        drawCircle(color)
    }
}

इस उदाहरण में, यह सर्कल को उस रंग से बनाता है जो मॉडिफ़ायर फ़ंक्शन में डाला गया है.

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

ये टाइप उपलब्ध हैं:

नोड

इस्तेमाल

सैंपल लिंक

LayoutModifierNode

एक Modifier.Node, जो रैप किए गए कॉन्टेंट को मेज़र करने और उसे लेआउट करने के तरीके में बदलाव करता है.

सैंपल

DrawModifierNode

लेआउट के स्पेस में खींचा गया Modifier.Node.

सैंपल

CompositionLocalConsumerModifierNode

इस इंटरफ़ेस को लागू करने से, आपके Modifier.Node को कॉम्पोज़िशन लोकल पढ़ने की अनुमति मिलती है.

सैंपल

SemanticsModifierNode

एक Modifier.Node, जो टेस्टिंग, सुलभता, और मिलते-जुलते इस्तेमाल के उदाहरणों में इस्तेमाल करने के लिए, सेमेटिक्स की/वैल्यू जोड़ता है.

सैंपल

PointerInputModifierNode

ऐसा Modifier.Node जिसे PointerInputChanges मिलता है.

सैंपल

ParentDataModifierNode

पैरंट लेआउट को डेटा देने वाला Modifier.Node.

सैंपल

LayoutAwareModifierNode

ऐसा Modifier.Node जिसे onMeasured और onPlaced कॉलबैक मिलते हैं.

सैंपल

GlobalPositionAwareModifierNode

ऐसा Modifier.Node जिसे लेआउट के आखिरी LayoutCoordinates के साथ onGloballyPositioned कॉलबैक मिलता है. ऐसा तब होता है, जब कॉन्टेंट की ग्लोबल पोज़िशन बदल गई हो.

सैंपल

ObserverModifierNode

ObserverNode लागू करने वाले Modifier.Node, onObservedReadsChanged को लागू करने का अपना तरीका दे सकते हैं. इसे observeReads ब्लॉक में पढ़े गए स्नैपशॉट ऑब्जेक्ट में हुए बदलावों के जवाब में कॉल किया जाएगा.

सैंपल

DelegatingNode

ऐसा Modifier.Node जो अन्य Modifier.Node इंस्टेंस को काम सौंप सकता है.

इसका इस्तेमाल, एक से ज़्यादा नोड को एक में जोड़ने के लिए किया जा सकता है.

सैंपल

TraversableNode

इससे Modifier.Node क्लास, एक ही टाइप की क्लास या किसी खास कुंजी के लिए, नोड ट्री में ऊपर/नीचे जा सकती हैं.

सैंपल

जब उनके संबंधित एलिमेंट पर अपडेट का कॉल किया जाता है, तो नोड अपने-आप अमान्य हो जाते हैं. हमारा उदाहरण DrawModifierNode है. इसलिए, जब भी एलिमेंट पर अपडेट का अनुरोध किया जाता है, तो नोड फिर से रेंडर करने की प्रोसेस शुरू करता है और उसका रंग सही तरीके से अपडेट हो जाता है. यहां दी गई जानकारी के मुताबिक, अपने-आप अमान्य होने की सुविधा से ऑप्ट आउट किया जा सकता है.

ModifierNodeElement

ModifierNodeElement एक ऐसी क्लास है जिसे बदला नहीं जा सकता. इसमें आपके कस्टम मॉडिफ़ायर को बनाने या अपडेट करने के लिए डेटा होता है:

// ModifierNodeElement
private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() {
    override fun create() = CircleNode(color)

    override fun update(node: CircleNode) {
        node.color = color
    }
}

ModifierNodeElement को लागू करने के लिए, इन तरीकों को बदलना होगा:

  1. create: यह फ़ंक्शन आपके मॉडिफ़ायर नोड को इंस्टैंशिएट करता है. आपका मॉडिफ़ायर पहली बार लागू होने पर, नोड बनाने के लिए इसे कॉल किया जाता है. आम तौर पर, इसका मतलब है कि नोड बनाना और उसे उन पैरामीटर के साथ कॉन्फ़िगर करना जिन्हें मॉडिफ़ायर फ़ैक्ट्री में पास किया गया था.
  2. update: जब भी इस मॉडिफ़ायर को उसी जगह पर दिया जाता है जहां यह नोड पहले से मौजूद होता है, लेकिन प्रॉपर्टी बदल जाती है, तब इस फ़ंक्शन को कॉल किया जाता है. यह क्लास के equals तरीके से तय होता है. पहले से बनाया गया मॉडिफ़ायर नोड, update कॉल में पैरामीटर के तौर पर भेजा जाता है. इस समय, आपको अपडेट किए गए पैरामीटर के हिसाब से नोड की प्रॉपर्टी अपडेट करनी चाहिए. इस तरह से नोड का फिर से इस्तेमाल किया जा सकता है. यह Modifier.Node की परफ़ॉर्मेंस को बेहतर बनाने के लिए ज़रूरी है. इसलिए, update तरीके से नया नोड बनाने के बजाय, आपको मौजूदा नोड को अपडेट करना होगा. हमारे सर्कल के उदाहरण में, नोड का रंग अपडेट हो गया है.

इसके अलावा, ModifierNodeElement को लागू करने के लिए, equals और hashCode को भी लागू करना होगा. update फ़ंक्शन सिर्फ़ तब कॉल किया जाएगा, जब पिछले एलिमेंट के साथ बराबर की तुलना करने पर, नतीजा 'गलत' दिखे.

ऊपर दिए गए उदाहरण में, ऐसा करने के लिए डेटा क्लास का इस्तेमाल किया गया है. इन तरीकों का इस्तेमाल यह जांचने के लिए किया जाता है कि किसी नोड को अपडेट करने की ज़रूरत है या नहीं. अगर आपके एलिमेंट में ऐसी प्रॉपर्टी हैं जिनसे यह तय नहीं होता कि किसी नोड को अपडेट करना है या नहीं या आपको बाइनरी के साथ काम करने की वजह से डेटा क्लास से बचना है, तो equals और hashCode को मैन्युअल तरीके से लागू किया जा सकता है. उदाहरण के लिए, पैडिंग मॉडिफ़ायर एलिमेंट.

मॉडिफ़ायर फ़ैक्ट्री

यह आपके मॉडिफ़ायर का सार्वजनिक एपीआई प्लैटफ़ॉर्म है. ज़्यादातर मामलों में, मॉडिफ़ायर एलिमेंट बनाकर उसे मॉडिफ़ायर चेन में जोड़ा जाता है:

// Modifier factory
fun Modifier.circle(color: Color) = this then CircleElement(color)

पूरा उदाहरण

ये तीनों हिस्से, Modifier.Node एपीआई का इस्तेमाल करके सर्कल बनाने के लिए, कस्टम मॉडिफ़ायर बनाने के लिए एक साथ काम करते हैं:

// Modifier factory
fun Modifier.circle(color: Color) = this then CircleElement(color)

// ModifierNodeElement
private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() {
    override fun create() = CircleNode(color)

    override fun update(node: CircleNode) {
        node.color = color
    }
}

// Modifier.Node
private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() {
    override fun ContentDrawScope.draw() {
        drawCircle(color)
    }
}

Modifier.Node का इस्तेमाल करने की सामान्य स्थितियां

Modifier.Node की मदद से कस्टम मॉडिफ़ायर बनाते समय, आपको इन सामान्य स्थितियों का सामना करना पड़ सकता है.

शून्य पैरामीटर

अगर आपके मॉडिफ़ायर में कोई पैरामीटर नहीं है, तो उसे कभी अपडेट करने की ज़रूरत नहीं होती. इसके अलावा, उसे डेटा क्लास बनाने की ज़रूरत भी नहीं होती. यहां ऐसे मॉडिफ़ायर को लागू करने का उदाहरण दिया गया है जो किसी कॉम्पोज़ेबल पर तय पैडिंग लागू करता है:

fun Modifier.fixedPadding() = this then FixedPaddingElement

data object FixedPaddingElement : ModifierNodeElement<FixedPaddingNode>() {
    override fun create() = FixedPaddingNode()
    override fun update(node: FixedPaddingNode) {}
}

class FixedPaddingNode : LayoutModifierNode, Modifier.Node() {
    private val PADDING = 16.dp

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        val paddingPx = PADDING.roundToPx()
        val horizontal = paddingPx * 2
        val vertical = paddingPx * 2

        val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))

        val width = constraints.constrainWidth(placeable.width + horizontal)
        val height = constraints.constrainHeight(placeable.height + vertical)
        return layout(width, height) {
            placeable.place(paddingPx, paddingPx)
        }
    }
}

कॉम्पोज़िशन लोकल का रेफ़रंस देना

Modifier.Node मॉडिफ़ायर, CompositionLocal जैसे Compose स्टेटस ऑब्जेक्ट में होने वाले बदलावों को अपने-आप नहीं देखते. Modifier.Node मॉडिफ़ायर का फ़ायदा, सिर्फ़ कॉम्पोज़ेबल फ़ैक्ट्री की मदद से बनाए गए मॉडिफ़ायर से यह है कि वे कॉम्पोज़िशन लोकल की वैल्यू को उस जगह से पढ़ सकते हैं जहां आपके यूआई ट्री में मॉडिफ़ायर का इस्तेमाल किया जाता है, न कि currentValueOf का इस्तेमाल करके, जहां मॉडिफ़ायर को असाइन किया जाता है.

हालांकि, मॉडिफ़ायर नोड इंस्टेंस, स्टेटस में होने वाले बदलावों को अपने-आप नहीं देखते. किसी कंपोज़िशन के लोकल वैरिएबल में होने वाले बदलाव पर अपने-आप प्रतिक्रिया देने के लिए, स्कोप में उसकी मौजूदा वैल्यू पढ़ी जा सकती है:

इस उदाहरण में, LocalContentColor की वैल्यू का इस्तेमाल करके, उसके रंग के आधार पर बैकग्राउंड बनाया गया है. ContentDrawScope, स्नैपशॉट में होने वाले बदलावों को मॉनिटर करता है. इसलिए, LocalContentColor की वैल्यू बदलने पर, यह अपने-आप फिर से रेंडर हो जाता है:

class BackgroundColorConsumerNode :
    Modifier.Node(),
    DrawModifierNode,
    CompositionLocalConsumerModifierNode {
    override fun ContentDrawScope.draw() {
        val currentColor = currentValueOf(LocalContentColor)
        drawRect(color = currentColor)
        drawContent()
    }
}

किसी स्कोप से बाहर की स्थिति में बदलाव होने पर प्रतिक्रिया देने और अपने मॉडिफ़ायर को अपने-आप अपडेट करने के लिए, ObserverModifierNode का इस्तेमाल करें.

उदाहरण के लिए, Modifier.scrollable इस तकनीक का इस्तेमाल करके, LocalDensity में हुए बदलावों को देखता है. इसका आसान उदाहरण यहां दिया गया है:

class ScrollableNode :
    Modifier.Node(),
    ObserverModifierNode,
    CompositionLocalConsumerModifierNode {

    // Place holder fling behavior, we'll initialize it when the density is available.
    val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(UnityDensity))

    override fun onAttach() {
        updateDefaultFlingBehavior()
        observeReads { currentValueOf(LocalDensity) } // monitor change in Density
    }

    override fun onObservedReadsChanged() {
        // if density changes, update the default fling behavior.
        updateDefaultFlingBehavior()
    }

    private fun updateDefaultFlingBehavior() {
        val density = currentValueOf(LocalDensity)
        defaultFlingBehavior.flingDecay = splineBasedDecay(density)
    }
}

ऐनिमेशन वाला मॉडिफ़ायर

Modifier.Node लागू करने वाले ऐप्लिकेशन के पास coroutineScope का ऐक्सेस होता है. इससे ऐनिमेट किए जा सकने वाले एपीआई को कंपोज करने की सुविधा का इस्तेमाल किया जा सकता है. उदाहरण के लिए, यह स्निपेट ऊपर दिए गए CircleNode को बार-बार फ़ेड इन और फ़ेड आउट करने के लिए बदलता है:

class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode {
    private val alpha = Animatable(1f)

    override fun ContentDrawScope.draw() {
        drawCircle(color = color, alpha = alpha.value)
        drawContent()
    }

    override fun onAttach() {
        coroutineScope.launch {
            alpha.animateTo(
                0f,
                infiniteRepeatable(tween(1000), RepeatMode.Reverse)
            ) {
            }
        }
    }
}

डेलिगेशन का इस्तेमाल करके, मॉडिफ़ायर के बीच स्टेटस शेयर करना

Modifier.Node मॉडिफ़ायर, दूसरे नोड को अपने काम सौंप सकते हैं. इसका इस्तेमाल कई कामों के लिए किया जा सकता है. जैसे, अलग-अलग मॉडिफ़ायर में सामान्य तरीके से लागू किए गए बदलावों को निकालना. हालांकि, इसका इस्तेमाल मॉडिफ़ायर के बीच सामान्य स्थिति शेयर करने के लिए भी किया जा सकता है.

उदाहरण के लिए, क्लिक किए जा सकने वाले ऐसे मॉडिफ़ायर नोड का बुनियादी लागू करना जो इंटरैक्शन डेटा शेयर करता है:

class ClickableNode : DelegatingNode() {
    val interactionData = InteractionData()
    val focusableNode = delegate(
        FocusableNode(interactionData)
    )
    val indicationNode = delegate(
        IndicationNode(interactionData)
    )
}

नोड के अपने-आप अमान्य होने की सुविधा से ऑप्ट आउट करना

जब उनसे जुड़े ModifierNodeElement कॉल अपडेट होते हैं, तो Modifier.Node नोड अपने-आप अमान्य हो जाते हैं. कभी-कभी, किसी ज़्यादा जटिल मॉडिफ़ायर में, आपको इस व्यवहार से ऑप्ट आउट करना पड़ सकता है, ताकि आपके मॉडिफ़ायर के फ़ेज़ अमान्य होने के समय पर ज़्यादा बेहतर तरीके से कंट्रोल किया जा सके.

यह खास तौर पर तब मददगार हो सकता है, जब आपका कस्टम मॉडिफ़ायर, लेआउट और ड्रॉ, दोनों में बदलाव करता हो. अपने-आप अमान्य होने की सुविधा से ऑप्ट आउट करने पर, सिर्फ़ color जैसी ड्रॉ से जुड़ी प्रॉपर्टी में बदलाव होने पर, ड्रॉ को अमान्य किया जा सकता है. साथ ही, लेआउट को अमान्य नहीं किया जा सकता. इससे आपके मॉडिफ़ायर की परफ़ॉर्मेंस बेहतर हो सकती है.

इसका एक काल्पनिक उदाहरण नीचे दिया गया है. इसमें ऐसे मॉडिफ़ायर का इस्तेमाल किया गया है जिसमें color, size, और onClick लेम्ब्डा प्रॉपर्टी हैं. यह मॉडिफ़ायर सिर्फ़ ज़रूरी एट्रिब्यूट को अमान्य करता है. साथ ही, अमान्य नहीं किए जाने वाले एट्रिब्यूट को स्किप कर देता है:

class SampleInvalidatingNode(
    var color: Color,
    var size: IntSize,
    var onClick: () -> Unit
) : DelegatingNode(), LayoutModifierNode, DrawModifierNode {
    override val shouldAutoInvalidate: Boolean
        get() = false

    private val clickableNode = delegate(
        ClickablePointerInputNode(onClick)
    )

    fun update(color: Color, size: IntSize, onClick: () -> Unit) {
        if (this.color != color) {
            this.color = color
            // Only invalidate draw when color changes
            invalidateDraw()
        }

        if (this.size != size) {
            this.size = size
            // Only invalidate layout when size changes
            invalidateMeasurement()
        }

        // If only onClick changes, we don't need to invalidate anything
        clickableNode.update(onClick)
    }

    override fun ContentDrawScope.draw() {
        drawRect(color)
    }

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        val size = constraints.constrain(size)
        val placeable = measurable.measure(constraints)
        return layout(size.width, size.height) {
            placeable.place(0, 0)
        }
    }
}