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

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

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

मॉडिफ़ायर, स्टैंडर्ड 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 मॉडिफ़ायर सेट करें. padding के start और end पैरामीटर, लेआउट की दिशा के हिसाब से काम करते हैं. लेआउट की दिशा चाहे जो भी हो, बाएं या दाएं किनारे पर पैडिंग जोड़ने के लिए, absolutePadding का इस्तेमाल करें. साथ ही, इसके left और right पैरामीटर का इस्तेमाल करें.

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

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

नीचे दिए गए उदाहरण में, चाइल्ड Spacer का साइज़, पैरंट 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 रिपॉज़िटरी देखें.

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