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

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 এবং অন্যান্য কম্পোজ স্টেট-ব্যাকড অ্যানিমেশন এপিআই- ও ব্যবহার করতে পারেন। উদাহরণস্বরূপ, নিম্নলিখিত কোড স্নিপেটটি এমন একটি মডিফায়ার দেখাচ্ছে যা এনাবল/ডিজেবল করা হলে আলফা পরিবর্তনের অ্যানিমেশন দেখায়:

@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-এ মডিফায়ার তৈরি করার জন্য একটি নিম্ন-স্তরের 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 যা তার আবৃত বিষয়বস্তুর পরিমাপ ও বিন্যাস পরিবর্তন করে।

নমুনা

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

যেসব Modifier.Node ইমপ্লিমেন্ট করে, ObserverNode onObservedReadsChanged এর নিজস্ব ইমপ্লিমেন্টেশন প্রদান করতে পারে, যা একটি observeReads ব্লকের মধ্যে পঠিত স্ন্যাপশট অবজেক্টের পরিবর্তনের প্রতিক্রিয়ায় কল করা হবে।

নমুনা

DelegatingNode

একটি Modifier.Node যা অন্য Modifier.Node ইনস্ট্যান্সগুলোকে কাজ অর্পণ করতে সক্ষম।

একাধিক নোড ইমপ্লিমেন্টেশনকে একীভূত করার জন্য এটি সহায়ক হতে পারে।

নমুনা

TraversableNode

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

নমুনা

যখন কোনো নোডের সংশ্লিষ্ট এলিমেন্টে 'update' কল করা হয়, তখন নোডগুলো স্বয়ংক্রিয়ভাবে অবৈধ হয়ে যায়। যেহেতু আমাদের উদাহরণটি একটি DrawModifierNode , তাই যখনই এলিমেন্টটিতে 'update' কল করা হয়, নোডটি পুনরায় আঁকা হয় এবং এর রঙ সঠিকভাবে আপডেট হয়। স্বয়ংক্রিয়-অবৈধকরণ থেকে বেরিয়ে আসা সম্ভব, যেমনটি 'নোড স্বয়ংক্রিয়-অবৈধকরণ থেকে বেরিয়ে আসুন' বিভাগে বিস্তারিতভাবে বলা হয়েছে।

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 তুলনা করলে false রিটার্ন করবে।

পূর্ববর্তী উদাহরণে এটি করার জন্য একটি ডেটা ক্লাস ব্যবহার করা হয়েছে। কোনো নোড আপডেট করার প্রয়োজন আছে কি না, তা পরীক্ষা করতে এই মেথডগুলো ব্যবহৃত হয়। যদি আপনার এলিমেন্টের এমন প্রোপার্টি থাকে যা কোনো নোড আপডেট করার ক্ষেত্রে ভূমিকা রাখে না, অথবা আপনি বাইনারি কম্প্যাটিবিলিটির কারণে ডেটা ক্লাস এড়িয়ে চলতে চান, তাহলে আপনি ম্যানুয়ালি equals এবং hashCode ইমপ্লিমেন্ট করতে পারেন, যেমন— প্যাডিং মডিফায়ার এলিমেন্ট ব্যবহার করে

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

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

// 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 মডিফায়ারগুলো CompositionLocal মতো Compose স্টেট অবজেক্টের পরিবর্তনগুলো স্বয়ংক্রিয়ভাবে পর্যবেক্ষণ করে না। শুধুমাত্র একটি কম্পোজেবল ফ্যাক্টরি দিয়ে তৈরি মডিফায়ারের তুলনায় 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)
    )
}

নোড স্বয়ংক্রিয় বাতিলকরণ থেকে অপ্ট আউট করুন

যখন সংশ্লিষ্ট ModifierNodeElement update` কল করে, তখন 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)
        }
    }
}