मॉडिफ़ायर लिखें

मॉडिफ़ायर की मदद से, किसी कॉम्पोज़ेबल को सजाया या बेहतर बनाया जा सकता है. मॉडिफ़ायर की मदद से ये काम किए जा सकते हैं:

  • कॉम्पोज़ेबल का साइज़, लेआउट, व्यवहार, और दिखावट बदलना
  • सुलभता लेबल जैसी जानकारी जोड़ना
  • उपयोगकर्ता के इनपुट को प्रोसेस करना
  • बेहतर इंटरैक्शन जोड़ें. जैसे, किसी एलिमेंट को क्लिक किया जा सकने वाला, स्क्रोल किया जा सकने वाला, खींचा और छोड़ा जा सकने वाला या ज़ूम किया जा सकने वाला बनाना

मॉडिफ़ायर, स्टैंडर्ड 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 पैरामीटर स्वीकार करें और उस मॉडिफ़ायर को अपने पहले चाइल्ड को पास करें, जो यूआई दिखाता है. ऐसा करने से, आपके कोड को फिर से इस्तेमाल करना आसान हो जाता है. साथ ही, इसके काम करने के तरीके का अनुमान लगाना और उसे समझना आसान हो जाता है. ज़्यादा जानकारी के लिए, 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 के माता-पिता के बच्चे के लिए किया जा सकता है.

स्कोप सेफ़्टी की सुविधा, ऐसे मॉडिफ़ायर जोड़ने से रोकती है जो अन्य कॉम्पोज़ेबल और स्कोप में काम नहीं करेंगे. साथ ही, यह आपको बार-बार कोशिश करने और गड़बड़ियों को ठीक करने में लगने वाले समय को बचाती है.

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

matchParentSize, Box में है

जैसा कि ऊपर बताया गया है, अगर आपको चाइल्ड लेआउट का साइज़, पैरंट लेआउट के साइज़ के बराबर रखना है, तो matchParentSize मॉडिफ़ायर का इस्तेमाल करें. इससे Box के साइज़ पर कोई असर नहीं पड़ेगा.Box

ध्यान दें कि matchParentSize सिर्फ़ Box के दायरे में उपलब्ध है. इसका मतलब है कि यह सिर्फ़ Box कंपोजेबल के डायरेक्ट चाइल्ड पर लागू होता है.

नीचे दिए गए उदाहरण में, चाइल्ड Spacer का साइज़, पैरंट Box से लिया जाता है. साथ ही, पैरंट Box का साइज़, सबसे बड़े चाइल्ड ArtistCard से लिया जाता है.

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

ग्रे बैकग्राउंड वाला कंटेनर

अगर matchParentSize के बजाय fillMaxSize का इस्तेमाल किया जाता है, तो Spacer, पैरंट के लिए उपलब्ध सभी जगह ले लेगा. इससे पैरंट का साइज़ बढ़ जाएगा और वह उपलब्ध सभी जगह को भर देगा.

स्क्रीन पर स्लेटी रंग का बैकग्राउंड

Row और Column में weight

जैसा कि आपने पैडिंग और साइज़ वाले पिछले सेक्शन में देखा है, डिफ़ॉल्ट रूप से किसी कॉम्पोज़ेबल का साइज़, उस कॉन्टेंट से तय होता है जिसे रैप किया जा रहा है. weight मॉडिफ़ायर का इस्तेमाल करके, किसी कंपोज़ेबल के साइज़ को उसके पैरंट में फ़्लेक्सिबल बनाया जा सकता है. यह मॉडिफ़ायर सिर्फ़ RowScope और ColumnScope में उपलब्ध है.

आइए, एक Row लेते हैं जिसमें दो Box कॉम्पोज़ेबल हैं. पहले बॉक्स को दूसरे बॉक्स के मुकाबले दोगुना 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 के रनटाइम पर कम काम पड़ता है. ऐसा तब होता है, जब 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
    )
}

यह सुविधा, लेज़ी लेआउट के साथ इस्तेमाल करने पर खास तौर पर फ़ायदेमंद हो सकती है. ज़्यादातर मामलों में, आपको अपने सभी सामान के लिए एक जैसे मॉडिफ़ायर चाहिए होंगे:

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 कोडलैब में बुनियादी लेआउट देखें या Android के रिपॉज़िटरी में अब उपलब्ध है लेख पढ़ें.

कस्टम मॉडिफ़ायर और उन्हें बनाने के तरीके के बारे में ज़्यादा जानने के लिए, कस्टम लेआउट - लेआउट मॉडिफ़ायर का इस्तेमाल करना दस्तावेज़ देखें.