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)
कंपोज़ेबल मॉडिफ़ायर फ़ैक्ट्री का इस्तेमाल करके, कस्टम मॉडिफ़ायर बनाना
किसी मौजूदा मॉडिफ़ायर को वैल्यू पास करने के लिए, ऐप्लिकेशन बनाने की सुविधा का इस्तेमाल करके कस्टम मॉडिफ़ायर भी बनाया जा सकता है. इसे कंपोज़ेबल मॉडिफ़ायर फ़ैक्ट्री कहा जाता है.
मॉडिफ़ायर बनाने के लिए, कंपोज़ेबल मॉडिफ़ायर फ़ैक्ट्री का इस्तेमाल करने पर, आपको ज़्यादा बेहतर Compose एपीआई इस्तेमाल करने की सुविधा भी मिलती है. जैसे, animate*AsState और 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 का इस्तेमाल करें. ऐसा इसलिए, क्योंकि कंपोज़िशन लोकल, इस्तेमाल की जाने वाली साइट पर सही तरीके से रिज़ॉल्व हो जाएंगे और उन्हें सुरक्षित तरीके से होस्ट किया जा सकता है.
कंपोज़ेबल फ़ंक्शन मॉडिफ़ायर कभी स्किप नहीं किए जाते
कंपोज़ेबल फ़ैक्ट्री मॉडिफ़ायर कभी भी स्किप नहीं किए जाते, क्योंकि रिटर्न वैल्यू वाले कंपोज़ेबल फ़ंक्शन स्किप नहीं किए जा सकते. इसका मतलब है कि हर बार रीकंपोज़िशन होने पर, आपके मॉडिफ़ायर फ़ंक्शन को कॉल किया जाएगा. अगर बार-बार रीकंपोज़िशन होता है, तो यह महंगा हो सकता है.
कंपोज़ेबल फ़ंक्शन मॉडिफ़ायर को कंपोज़ेबल फ़ंक्शन के अंदर ही कॉल किया जाना चाहिए
सभी कंपोज़ेबल फ़ंक्शन की तरह, कंपोज़ेबल फ़ैक्ट्री मॉडिफ़ायर को कंपोज़िशन के अंदर से कॉल किया जाना चाहिए. इससे यह तय होता है कि मॉडिफ़ायर को कहां तक होस्ट किया जा सकता है, क्योंकि इसे कंपोज़िशन से बाहर होस्ट नहीं किया जा सकता. वहीं, कंपोज़ नहीं किए जा सकने वाले मॉडिफ़ायर फ़ैक्ट्री को कंपोज़ेबल फ़ंक्शन से बाहर निकाला जा सकता है. इससे उन्हें आसानी से दोबारा इस्तेमाल किया जा सकता है और परफ़ॉर्मेंस को बेहतर बनाया जा सकता है:
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 को लागू करता है. इससे, ड्रॉ करने के तरीके को बदला जा सकता है.
ये टाइप उपलब्ध हैं:
नोड |
इस्तेमाल |
सैंपल लिंक |
एक |
||
एक |
||
इस इंटरफ़ेस को लागू करने से, आपका |
||
एक |
||
|
||
एक |
||
एक |
||
एक |
||
|
||
एक यह कई नोड को एक साथ लागू करने के लिए मददगार हो सकता है. |
||
इससे |
जब किसी एलिमेंट को अपडेट किया जाता है, तो उससे जुड़े नोड अपने-आप अमान्य हो जाते हैं. हमारे उदाहरण में 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 को लागू करने के लिए, इन तरीकों को बदलना होगा:
create: यह वह फ़ंक्शन है जो आपके मॉडिफ़ायर नोड को इंस्टैंशिएट करता है. जब पहली बार मॉडिफ़ायर लागू किया जाता है, तब नोड बनाने के लिए इसे कॉल किया जाता है. आम तौर पर, इसका मतलब है कि नोड को बनाना और उसे उन पैरामीटर के साथ कॉन्फ़िगर करना जो मॉडिफ़ायर फ़ैक्ट्री में पास किए गए थे.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 मॉडिफ़ायर, Compose के स्टेट ऑब्जेक्ट में होने वाले बदलावों को अपने-आप मॉनिटर नहीं करते. जैसे, CompositionLocal. Modifier.Node मॉडिफ़ायर, कंपोज़ेबल फ़ैक्ट्री से बनाए गए मॉडिफ़ायर से बेहतर होते हैं. ऐसा इसलिए, क्योंकि ये आपके यूज़र इंटरफ़ेस (यूआई) ट्री में उस जगह से कंपोज़िशन लोकल की वैल्यू को पढ़ सकते हैं जहां मॉडिफ़ायर का इस्तेमाल किया गया है. ये उस जगह से वैल्यू नहीं पढ़ते जहां मॉडिफ़ायर को असाइन किया गया है. इसके लिए, currentValueOf का इस्तेमाल किया जाता है.
हालांकि, मॉडिफ़ायर नोड इंस्टेंस, स्टेटस में होने वाले बदलावों को अपने-आप मॉनिटर नहीं करते. कंपोज़िशन लोकल में होने वाले बदलावों पर अपने-आप प्रतिक्रिया देने के लिए, किसी स्कोप में इसकी मौजूदा वैल्यू को पढ़ा जा सकता है:
DrawModifierNode:ContentDrawScopeLayoutModifierNode:MeasureScopeऔरIntrinsicMeasureScopeSemanticsModifierNode:SemanticsPropertyReceiver
इस उदाहरण में, 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 नोड, उनसे जुड़े 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) } } }