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

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

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

मॉडिफ़ायर, स्टैंडर्ड 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, कस्टम स्कोप की मदद से इसे लागू करता है.

उदाहरण के लिए, अगर आपको किसी बच्चे को माता-पिता 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 रिपॉज़िटरी देखें.

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