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

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

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

मॉडिफ़ायर, 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
    }
}

पूरा एरिया, जिसमें किनारों पर मौजूद पैडिंग भी शामिल है, क्लिक करने पर दिखता है

ऊपर दिए गए कोड में, पूरा एरिया क्लिक किया जा सकता है. इसमें आस-पास का पैडिंग भी शामिल है. ऐसा इसलिए है, क्योंकि padding मॉडिफ़ायर को clickable मॉडिफ़ायर के बाद लागू किया गया है. अगर मॉडिफ़ायर का क्रम बदल दिया जाता है, तो 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 पैरंट के चाइल्ड पर किया जा सकता है.

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

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

Box में matchParentSize

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

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