মডিফায়ার কম্পোজ করা

মডিফায়ার আপনাকে কোনও কম্পোজ করার উপযুক্ত আইটেমকে সাজাতে বা আরও উন্নত করতে দেয়। মডিফায়ার আপনাকে এই ধরনের কাজ করতে দেয়:

  • কম্পোজ করার উপযুক্ত আইটেমের সাইজ, লেআউট, আচরণ ও চেহারা পরিবর্তন করা
  • অ্যাক্সেসিবিলিটি লেবেলের মতো তথ্য যোগ করুন
  • ব্যবহারকারীর ইনপুট প্রসেস করা
  • কোনও এলিমেন্টকে ক্লিক করা যায়, স্ক্রল করা যায়, ড্র্যাগ করা যায় বা জুম করা যায় এমন হাই-লেভেল ইন্টার‍্যাকশন যোগ করুন

মডিফায়ার হল স্ট্যান্ডার্ড Kotlin অবজেক্ট। Modifier ক্লাস ফাংশনগুলির মধ্যে একটি কল করে একটি মডিফায়ার তৈরি করুন:

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

রঙিন ব্যাকগ্রাউন্ডের উপর টেক্সটের দুটি লাইন, টেক্সটের চারপাশে প্যাডিং।

এগুলি কম্পোজ করতে আপনি এই ফাংশনগুলি একসাথে চেইন করতে পারবেন:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

টেক্সটের পিছনের রঙিন ব্যাকগ্রাউন্ড এখন ডিভাইসের সম্পূর্ণ প্রস্থ জুড়ে দেখানো হয়।

উপরের কোডে, একসাথে ব্যবহার করা বিভিন্ন মডিফায়ার ফাংশন লক্ষ্য করুন।

  • padding এলিমেন্টের চারপাশে স্পেস দেয়।
  • fillMaxWidth কম্পোজ করার উপযুক্ত আইটেমকে এর পেরেন্ট থেকে পাওয়া সর্বাধিক প্রস্থে পূর্ণ করে।

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

মডিফায়ারের ক্রম গুরুত্বপূর্ণ

মডিফায়ার ফাংশনের ক্রম গুরুত্বপূর্ণ। যেহেতু প্রতিটি ফাংশন Modifierআগের ফাংশনের রিটার্ন ভ্যালুতে পরিবর্তন করে, তাই সিকোয়েন্স ফাইনাল ফলাফলের উপর প্রভাব ফেলে। এর একটি উদাহরণ দেখা যাক:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

মার্জিনের চারপাশের প্যাডিং সহ পুরো জায়গাটি ক্লিক করলে প্রতিক্রিয়া জানায়

উপরের কোডে, চারপাশের প্যাডিং সহ পুরো জায়গাটি ক্লিক করা যায়, কারণ clickable মডিফায়ার padding মডিফায়ার পরে প্রয়োগ করা হয়েছে। মডিফায়ারগুলির ক্রম উল্টে গেলে, padding-এর যোগ করা স্পেস ব্যবহারকারীর ইনপুটের প্রতিক্রিয়া জানায় না:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

লেআউটের প্রান্তের চারপাশে থাকা প্যাডিং আর ক্লিকে প্রতিক্রিয়া জানায় না

বিল্ট-ইন মডিফায়ার

Jetpack Compose, কম্পোজ করার উপযুক্ত আইটেমকে সাজাতে বা আরও উন্নত করতে সাহায্য করার জন্য বিল্ট-ইন মডিফায়ারদের একটি তালিকা প্রদান করে। আপনার লেআউট অ্যাডজাস্ট করার জন্য এখানে কিছু সাধারণ মডিফায়ার দেওয়া হল।

padding ও size

ডিফল্ট হিসেবে, Compose-এ দেওয়া লেআউট তার চাইল্ডকে র‍্যাপ করে। তবে, আপনি size মডিফায়ার ব্যবহার করে সাইজ সেট করতে পারবেন:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

মনে রাখবেন, আপনি যে সাইজ নির্দিষ্ট করেছেন সেটি মেনে নাও চলা হতে পারে যদি সেটি লেআউটের পেরেন্ট থেকে আসা বাধ্যবাধকতা পূরণ না করে। ইনকামিং সীমাবদ্ধতা নির্বিশেষে কম্পোজেবল সাইজ ফিক্স করতে চাইলে, requiredSize মডিফায়ার ব্যবহার করুন:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

বাচ্চার ছবি তার অভিভাবকের থেকে পাওয়া সীমাবদ্ধতার চেয়ে বড়

এই উদাহরণে, প্যারেন্ট height 100.dp হিসেবে সেট করা থাকলেও, Image-এর উচ্চতা 150.dp হবে, কারণ requiredSize মডিফায়ারকে অগ্রাধিকার দেওয়া হয়।

আপনি যদি চান যে চাইল্ড লেআউটটি প্যারেন্ট লেআউটের অনুমোদিত সমস্ত উপলভ্য উচ্চতা পূরণ করুক, তাহলে fillMaxHeight মডিফায়ার যোগ করুন (Compose-এ fillMaxSize এবং fillMaxWidth-ও উপলভ্য):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

ছবির দৈর্ঘ্য তার পেরেন্ট এলিমেন্টের সমান

কোনও এলিমেন্টের চারপাশে প্যাডিং যোগ করতে, একটি padding মডিফায়ার সেট করুন।

আপনি যদি টেক্সট বেসলাইনের উপরে এমন প্যাডিং যোগ করতে চান যাতে লেআউটের উপর থেকে বেসলাইন পর্যন্ত একটি নির্দিষ্ট দূরত্ব তৈরি হয়, তাহলে paddingFromBaseline মডিফায়ার ব্যবহার করুন:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

টেক্সটের উপরে প্যাডিং

অফসেট

লেআউটকে তার আসল পজিশনের সাপেক্ষে পজিশন করতে, offset মডিফায়ার যোগ করুন এবং x ও y অ্যাক্সিসে অফসেট সেট করুন। অফসেট পজিটিভ ও নন-পজিটিভ দুইই হতে পারে। padding ও offset-এর মধ্যে পার্থক্য হল, কোনও কম্পোজ করার উপযুক্ত আইটেমে offset যোগ করলে, সেটির মাপ পরিবর্তন হয় না:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

টেক্সটটি তার পেরেন্ট কন্টেনারের ডানদিকে শিফট করা হয়েছে

লেআউটের দিকনির্দেশ অনুযায়ী offset মডিফায়ার অনুভূমিকভাবে প্রয়োগ করা হয়। বাঁদিক থেকে ডানদিকে কনটেক্সটে, পজিটিভ offset এলিমেন্টকে ডানদিকে সরিয়ে দেয়, অন্যদিকে ডানদিক থেকে বাঁদিকে কনটেক্সটে, এটি এলিমেন্টকে বাঁদিকে সরিয়ে দেয়। লেআউট দিকনির্দেশ বিবেচনা না করে অফসেট সেট করতে চাইলে, absoluteOffset মডিফায়ার দেখুন, যেখানে পজিটিভ অফসেট ভ্যালু সবসময় এলিমেন্টকে ডানদিকে সরায়।

offset মডিফায়ার দুটি ওভারলোড প্রদান করে - offset যা প্যারামিটার হিসেবে অফসেট নেয় এবং offset যা ল্যাম্বডা নেয়। কখন কোনটি ব্যবহার করতে হবে এবং পারফর্ম্যান্সের জন্য কীভাবে অপ্টিমাইজ করতে হবে সেই সম্পর্কে আরও বিস্তারিত তথ্য পেতে, কম্পোজ পারফর্ম্যান্স - যতদূর সম্ভব রিড ডিফার করুন বিভাগটি পড়ুন।

Compose-এ স্কোপের নিরাপত্তা

Compose-এ, এমন মডিফায়ার আছে যা শুধুমাত্র নির্দিষ্ট কম্পোজ করার মতো আইটেমের চাইল্ড এ প্রয়োগ করা হলে ব্যবহার করা যায়। Compose কাস্টম স্কোপের মাধ্যমে এটি এনফোর্স করে।

যেমন, আপনি যদি Box সাইজের উপর প্রভাব না ফেলে Box কোনও বাচ্চাকে তার অভিভাবকের সমান বড় করতে চান, তাহলে matchParentSize মডিফায়ার ব্যবহার করুন। matchParentSize শুধুমাত্র BoxScope-এ উপলভ্য। তাই, এটি শুধুমাত্র Box অভিভাবকের সন্তানের ক্ষেত্রেই ব্যবহার করা যাবে।

স্কোপ সেফটি আপনাকে এমন মডিফায়ার যোগ করা থেকে আটকায় যা অন্যান্য কম্পোজেবল ও স্কোপে কাজ করে না এবং ট্রায়াল ও এরর থেকে সময় বাঁচায়।

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

Box-এ matchParentSize

উপরে উল্লেখ করা হয়েছে, আপনি যদি চান যে চাইল্ড লেআউটটি প্যারেন্ট লেআউটের সমান সাইজের হোক Box এবং Box সাইজের উপর কোনও প্রভাব না পড়ুক, তাহলে matchParentSize মডিফায়ার ব্যবহার করুন।

মনে রাখবেন, matchParentSize শুধুমাত্র Box স্কোপের মধ্যেই উপলভ্য, অর্থাৎ এটি শুধুমাত্র Box কম্পোজেবলের সরাসরি চাইল্ডের ক্ষেত্রেই প্রযোজ্য।

নিচের উদাহরণে, Spacer চাইল্ডটি Box প্যারেন্টের থেকে সাইজ নিয়েছে, যা আবার সবচেয়ে বড় চাইল্ডের থেকে সাইজ নিয়েছে, ArtistCard এই ক্ষেত্রে।

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

কন্টেনার ভর্তি করা ধূসর ব্যাকগ্রাউন্ড

matchParentSize-এর পরিবর্তে fillMaxSize ব্যবহার করা হলে, Spacer পেরেন্টকে দেওয়া সব উপলভ্য জায়গা নিয়ে নেবে, এর ফলে পেরেন্টকে আরও জায়গা নিতে হবে এবং সব উপলভ্য জায়গা ভর্তি হয়ে যাবে।

স্ক্রিন জুড়ে ধূসর ব্যাকগ্রাউন্ড

Row ও Column-এ weight

প্যাডিং ও সাইজ সংক্রান্ত আগের বিভাগে আপনি যেমন দেখেছেন, ডিফল্ট হিসেবে, কম্পোজ করার উপযুক্ত আইটেমের সাইজ সেটির মধ্যে থাকা কন্টেন্ট দ্বারা নির্ধারিত হয়। weight Modifier ব্যবহার করে আপনি কম্পোজেবলের সাইজকে এর প্যারেন্টের মধ্যে ফ্লেক্সিবল হিসেবে সেট করতে পারবেন। এটি শুধুমাত্র RowScope এবং ColumnScope-এ উপলভ্য।

দুটি Box কম্পোজেবল আছে এমন একটি Row নেওয়া যাক। প্রথম বক্সের weight দ্বিতীয়টির দ্বিগুণ, তাই এর প্রস্থ দ্বিগুণ হবে। যেহেতু Row হল 210.dp চওড়া, প্রথম Box হল 140.dp চওড়া এবং দ্বিতীয়টি হল 70.dp:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

ছবির প্রস্থ টেক্সটের প্রস্থের দ্বিগুণ

মডিফায়ার এক্সট্র্যাক্ট ও আবার ব্যবহার করা

কোনও কম্পোজ করার উপযুক্ত আইটেমকে সাজাতে বা আরও উন্নত করতে একাধিক মডিফায়ার একসাথে চেইন করা যেতে পারে। এই চেইনটি Modifier ইন্টারফেসের মাধ্যমে তৈরি করা হয় যা একটি সাজানো, অপরিবর্তনীয় সিঙ্গেল Modifier.Elements-এর তালিকা দেখায়।

প্রতিটি Modifier.Element একটি করে স্বতন্ত্র আচরণকে বোঝায়, যেমন লেআউট, ড্রয়িং ও গ্রাফিক্স আচরণ, সব জেসচার-সম্পর্কিত, ফোকাস ও শব্দার্থ আচরণ, সেইসাথে ডিভাইস ইনপুট ইভেন্ট। সেগুলির ক্রম গুরুত্বপূর্ণ: মডিফায়ার এলিমেন্ট যেগুলি প্রথমে যোগ করা হবে সেগুলি প্রথমে প্রয়োগ করা হবে।

কখনও কখনও, একাধিক কম্পোজ করার উপযুক্ত আইটেমে একই মডিফায়ার চেনের ইনস্ট্যান্স আবার ব্যবহার করা উপকারী হতে পারে। এর জন্য, সেগুলিকে ভেরিয়েবলে এক্সট্র্যাক্ট করে উচ্চতর স্কোপে হোইস্ট করতে হবে। এটি কোডের পঠনযোগ্যতা উন্নত করতে পারে অথবা কয়েকটি কারণে আপনার অ্যাপের পারফর্ম্যান্স উন্নত করতে সাহায্য করতে পারে:

  • যেসব কম্পোজ করার মতো আইটেম এগুলি ব্যবহার করে, সেগুলির জন্য আবার কম্পোজ করার সময় মডিফায়ার আবার অ্যাসাইন করা হবে না
  • মডিফায়ার চেইন খুব দীর্ঘ ও জটিল হতে পারে, তাই চেইনের একই ইনস্ট্যান্স আবার ব্যবহার করলে, সেগুলি তুলনা করার সময় Compose রানটাইমকে যেসব কাজ করতে হয়, তা কমে যায়
  • এই এক্সট্র্যাকশন কোডবেস জুড়ে কোডের পরিচ্ছন্নতা, সামঞ্জস্য ও রক্ষণযোগ্যতা উন্নত করে

মডিফায়ার আবার ব্যবহার করার পেশাদার পদ্ধতি

আপনার নিজস্ব Modifier চেইন তৈরি করুন এবং একাধিক কম্পোজ করার উপযুক্ত কম্পোনেন্টে পুনর্ব্যবহার করার জন্য সেগুলি এক্সট্র্যাক্ট করুন। শুধু মডিফায়ার সেভ করা যায়, কারণ সেগুলি ডেটা-লাইক অবজেক্ট:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

ঘন ঘন পরিবর্তন হওয়া স্টেট লক্ষ্য করার সময় মডিফায়ার এক্সট্র্যাক্ট ও আবার ব্যবহার করা

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

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

পরিবর্তে, আপনি মডিফায়ারের একই ইনস্ট্যান্স তৈরি, এক্সট্র্যাক্ট ও আবার ব্যবহার করতে পারেন এবং এটিকে কম্পোজ করার উপযুক্ত আইটেমে এইভাবে পাস করতে পারেন:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

আনস্কোপড মডিফায়ার এক্সট্র্যাক্ট ও আবার ব্যবহার করা

মডিফায়ার আনস্কোপ করা বা নির্দিষ্ট কম্পোজ করার উপযুক্ত আইটেমের সাথে স্কোপ করা যেতে পারে। আনস্কোপড মডিফায়ারের ক্ষেত্রে, আপনি সহজেই সেগুলিকে যেকোনও কম্পোজ করার উপযুক্ত আইটেমের বাইরে সাধারণ ভেরিয়েবল হিসেবে এক্সট্র্যাক্ট করতে পারবেন:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

এটি বিশেষ করে Lazy লেআউটের সাথে ব্যবহার করলে সুবিধা পাওয়া যায়। বেশিরভাগ ক্ষেত্রে, আপনি চাইবেন যে আপনার সম্ভাব্য গুরুত্বপূর্ণ আইটেমের পরিমাণ যেন ঠিক একই মডিফায়ার থাকে:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

স্কোপ করা মডিফায়ার এক্সট্র্যাক্ট ও আবার ব্যবহার করা

নির্দিষ্ট কম্পোজেবলগুলিতে স্কোপ করা মডিফায়ার নিয়ে কাজ করার সময়, আপনি সেগুলিকে সম্ভাব্য সর্বোচ্চ লেভেলে এক্সট্র্যাক্ট করতে এবং যেখানে উপযুক্ত সেখানে আবার ব্যবহার করতে পারবেন:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

আপনাকে শুধু এক্সট্র্যাক্ট করা, স্কোপ করা মডিফায়ারগুলিকে একই স্কোপের, ডাইরেক্ট চিলড্রেনদের কাছে পাস করতে হবে। এটি কেন গুরুত্বপূর্ণ সেই বিষয়ে আরও রেফারেন্স পেতে, Compose-এ স্কোপ নিরাপত্তা বিভাগটি দেখুন:

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

এক্সট্র্যাক্ট করা মডিফায়ার আরও চেইন করা

আপনি .then() ফাংশন কল করে আপনার এক্সট্র্যাক্ট করা মডিফায়ার চেনের সাথে আরও চেন বা অ্যাপেন্ড করতে পারবেন:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

শুধু মনে রাখবেন যে মডিফায়ারগুলির ক্রম গুরুত্বপূর্ণ!

আরও জানুন

আমরা মডিফায়ারের সম্পূর্ণ তালিকা প্রদান করি, সাথে সেগুলির প্যারামিটার ও স্কোপও থাকে।

কীভাবে মডিফায়ার ব্যবহার করতে হয় সেই বিষয়ে আরও প্র্যাক্টিস করতে, আপনি Compose কোডল্যাবে প্রাথমিক লেআউট দেখতে পারেন অথবা Now in Android রিপোজিটরি দেখতে পারেন।

কাস্টম মডিফায়ার ও সেগুলি কীভাবে তৈরি করতে হয় সেই বিষয়ে আরও জানতে, কাস্টম লেআউট - লেআউট মডিফায়ার ব্যবহার করা ডকুমেন্টেশন দেখুন।