কাস্টম মডিফায়ার তৈরি করা

Compose-এ সাধারণ আচরণের জন্য অনেক মডিফায়ার আগে থেকেই দেওয়া থাকে, তবে আপনি নিজের কাস্টম মডিফায়ারও তৈরি করতে পারেন।

মডিফায়ারের একাধিক অংশ থাকে:

  • মডিফায়ার ফ্যাক্টরি
    • এটি Modifier-এর একটি এক্সটেনশন ফাংশন, যা আপনার মডিফায়ারের জন্য একটি ইডিওম্যাটিক API প্রদান করে এবং মডিফায়ারদের একসাথে চেইন করার অনুমতি দেয়। Compose আপনার UI পরিবর্তন করতে যেসব মডিফায়ার এলিমেন্ট ব্যবহার করে, সেগুলিকে মডিফায়ার ফ্যাক্টরি তৈরি করে।
  • একটি মডিফায়ার এলিমেন্ট
    • এখানে আপনি মডিফায়ারের আচরণ প্রয়োগ করতে পারবেন।

প্রয়োজনীয় কার্যকারিতার উপর নির্ভর করে কাস্টম মডিফায়ার প্রয়োগ করার একাধিক উপায় আছে। প্রায়শই, কাস্টম মডিফায়ার প্রয়োগ করার সবচেয়ে সহজ উপায় হল কাস্টম মডিফায়ার ফ্যাক্টরি প্রয়োগ করা যা আগে থেকেই সংজ্ঞায়িত করা অন্যান্য মডিফায়ার ফ্যাক্টরিকে একত্রিত করে। আরও কাস্টম আচরণ প্রয়োজন হলে, Modifier.Node API ব্যবহার করে মডিফায়ার এলিমেন্ট প্রয়োগ করুন, যা লোয়ার লেভেল হলেও আরও বেশি ফ্লেক্সিবিলিটি প্রদান করে।

আগে থেকে থাকা মডিফায়ার একসাথে চেইন করা

আগে থেকে থাকা মডিফায়ার ব্যবহার করে কাস্টম মডিফায়ার তৈরি করা প্রায়ই সম্ভব হয়। যেমন, 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 স্টেট ব্যাকড অ্যানিমেশন API-এর মতো হাই লেভেল কম্পোজ 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 ব্যবহার করে কাস্টম মডিফায়ার আচরণ প্রয়োগ করা

Compose-এ মডিফায়ার তৈরি করার জন্য Modifier.Node হল একটি লোয়ার লেভেল API। এটি সেই একই API যেখানে 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 যা এর র‍্যাপ করা কন্টেন্ট কীভাবে পরিমাপ ও লেআউট করা হয় তা পরিবর্তন করে।

Sample

DrawModifierNode

লেআউটের স্পেসে ড্র করে এমন Modifier.Node।

Sample

CompositionLocalConsumerModifierNode

এই ইন্টারফেস প্রয়োগ করলে আপনার Modifier.Node কম্পোজিশন লোকালে পড়তে পারবে।

Sample

SemanticsModifierNode

Modifier.Node যা টেস্টিং, অ্যাক্সেসিবিলিটি ও এই ধরনের ব্যবহারের ক্ষেত্রে ব্যবহার করার জন্য সিম্যান্টিক কী/ভ্যালু যোগ করে।

Sample

PointerInputModifierNode

PointerInputChanges পাওয়া Modifier.Node।

Sample

ParentDataModifierNode

এমন Modifier.Node যা পেরেন্ট লেআউটে ডেটা প্রদান করে।

Sample

LayoutAwareModifierNode

Modifier.Node যা onMeasured ও onPlaced কলব্যাক পায়।

Sample

GlobalPositionAwareModifierNode

A Modifier.Node যা কন্টেন্টের গ্লোবাল পজিশন পরিবর্তিত হয়ে যেতে পারে এমন অবস্থায় লেআউটের চূড়ান্ত LayoutCoordinates সহ onGloballyPositioned কলব্যাক পায়।

Sample

ObserverModifierNode

Modifier.Nodes যেগুলি ObserverNode প্রয়োগ করে, সেগুলি onObservedReadsChanged-এর নিজস্ব প্রয়োগ প্রদান করতে পারে যা observeReads ব্লকের মধ্যে পড়া স্ন্যাপশট অবজেক্টে করা পরিবর্তনের উত্তরে কল করা হবে।

Sample

DelegatingNode

এমন একটি Modifier.Node যা অন্যান্য Modifier.Node ইনস্ট্যান্সকে কাজ অ্যাসাইন করতে পারে।

একাধিক নোড ইমপ্লিমেন্টেশনকে একটিতে কম্পোজ করার জন্য এটি কাজে লাগতে পারে।

Sample

TraversableNode

Modifier.Node ক্লাসকে একই ধরনের ক্লাসের জন্য বা নির্দিষ্ট কী-এর জন্য নোড ট্রি উপরে/নিচে ট্রাভার্স করার অনুমতি দেয়।

Sample

নোডের সংশ্লিষ্ট এলিমেন্টে আপডেট কল করা হলে, নোড অটোমেটিক বাতিল হয়ে যায়। যেহেতু আমাদের উদাহরণটি একটি 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 প্রয়োগ করতে পারেন, যেমন প্যাডিং মডিফায়ার এলিমেন্ট।

মডিফায়ার ফ্যাক্টরি

এটি হল আপনার মডিফায়ারের পাবলিক API সারফেস। বেশিরভাগ ইমপ্লিমেন্টেশন মডিফায়ার এলিমেন্ট তৈরি করে এবং মডিফায়ার চেনে যোগ করে:

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

সম্পূর্ণ উদাহরণ

এই তিনটি অংশ একসাথে Modifier.Node API ব্যবহার করে একটি বৃত্ত আঁকার জন্য কাস্টম মডিফায়ার তৈরি করে:

// 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 ব্যবহার করে কম্পোজিশন লোকালের ভ্যালু পড়তে পারে। মডিফায়ার যেখানে অ্যালোকেট করা হয়েছে সেখানে নয়, আপনার UI ট্রি-তে যেখানে মডিফায়ার ব্যবহার করা হয়েছে সেখান থেকে এই ভ্যালু পড়া হয়।

তবে, মডিফায়ার নোড ইনস্ট্যান্স অটোমেটিক স্টেট পরিবর্তন লক্ষ্য করে না। কম্পোজিশনের লোকাল পরিবর্তন হলে অটোমেটিক প্রতিক্রিয়া জানাতে, আপনি স্কোপের মধ্যে এর বর্তমান ভ্যালু পড়তে পারেন:

এই উদাহরণে, 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)
        }
    }
}