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

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

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

मॉडिफ़ायर, स्टैंडर्ड 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 के दिशा-निर्देश देखें. साथ ही, Elements accept and respect a Modifier parameter देखें.

मॉडिफ़ायर का क्रम मायने रखता है

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

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

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

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

matchParentSize, Box में है

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

ध्यान दें कि 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 रनटाइम को तुलना करते समय कम वर्कलोड की ज़रूरत पड़ सकती है
  • इस एक्सट्रैक्शन से, कोडबेस में कोड को व्यवस्थित, एक जैसा, और रखरखाव में आसान बनाने में मदद मिलती है

मॉडिफ़ायर का फिर से इस्तेमाल करने के सबसे सही तरीके

अपनी 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 में बुनियादी लेआउट कोडलैब पर जाएं. इसके अलावा, Now in Android रिपॉज़िटरी देखें.

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