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

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

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

मॉडिफ़ायर, स्टैंडर्ड 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
    }
}

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

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

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

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

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

Box में matchParentSize

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

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

अपनी 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 रिपॉज़िटरी देखें.

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