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

Compose में, सामान्य व्यवहारों के लिए कई मॉडिफ़ायर उपलब्ध होते हैं. हालांकि, आपके पास अपने हिसाब से कस्टम मॉडिफ़ायर बनाने का विकल्प भी होता है.

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

  • मॉडिफ़ायर फ़ैक्ट्री
    • यह 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 state backed animation APIs जैसे ज़्यादा लेवल वाले Compose API का इस्तेमाल किया जा सकता है. उदाहरण के लिए, यहां दिए गए स्निपेट में एक ऐसा मॉडिफ़ायर दिखाया गया है जो चालू/बंद होने पर ऐल्फ़ा में बदलाव करता है:

@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 का इस्तेमाल करें. ऐसा इसलिए, क्योंकि कंपोज़िशन लोकल को इस्तेमाल की जा रही साइट पर सही तरीके से हल किया जाएगा और उन्हें सुरक्षित तरीके से ऊपर ले जाया जा सकता है.

कंपोज़ेबल फ़ंक्शन मॉडिफ़ायर कभी भी नहीं छोड़े जाते

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

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

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

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 की परफ़ॉर्मेंस को बेहतर बनाने में अहम भूमिका निभाती है. इसलिए, आपको 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. 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 का ऐक्सेस होता है. इससे Compose Animatable API का इस्तेमाल किया जा सकता है. उदाहरण के लिए, यह स्निपेट ऊपर दिए गए CircleNode में बदलाव करता है, ताकि वह बार-बार फ़ेड इन और फ़ेड आउट हो:

class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode {
    private lateinit var alpha: Animatable<Float, AnimationVector1D>

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

    override fun onAttach() {
        alpha = Animatable(1f)
        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)
    )
}

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

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

अगर आपका कस्टम मॉडिफ़ायर, लेआउट और ड्रॉ, दोनों में बदलाव करता है, तो यह खास तौर पर मददगार हो सकता है. अपने-आप अमान्य होने की सुविधा से ऑप्ट आउट करने पर, सिर्फ़ ड्रॉ से जुड़ी प्रॉपर्टी, जैसे कि 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)
        }
    }
}