ग्राफ़िक मॉडिफ़ायर

Compose में Canvas कंपोज़ेबल के अलावा, काम के कई ग्राफ़िक Modifiers भी होते हैं. इनसे कस्टम कॉन्टेंट बनाने में मदद मिलती है. ये मॉडिफ़ायर इसलिए काम के हैं, क्योंकि इन्हें किसी भी कंपोज़ेबल पर लागू किया जा सकता है.

ड्रॉइंग मॉडिफ़ायर

Compose में, ड्राइंग से जुड़े सभी निर्देश, ड्राइंग मॉडिफ़ायर की मदद से दिए जाते हैं. Compose में ड्राइंग के तीन मुख्य मॉडिफ़ायर होते हैं:

ड्रॉइंग के लिए बेस मॉडिफ़ायर drawWithContent है. इसकी मदद से, यह तय किया जा सकता है कि आपके कंपोज़ेबल को किस क्रम में ड्रॉ किया जाए. साथ ही, मॉडिफ़ायर के अंदर ड्रॉइंग के लिए कौनसे निर्देश दिए जाएं. drawBehind, drawWithContent के चारों ओर एक सुविधाजनक रैपर है. इसमें ड्रॉइंग का क्रम, कंपोज़ेबल के कॉन्टेंट के पीछे सेट किया गया है. drawWithCache इसके अंदर, onDrawBehind या onDrawWithContent को कॉल करता है. साथ ही, इनमें बनाए गए ऑब्जेक्ट को कैश मेमोरी में सेव करने का तरीका बताता है.

Modifier.drawWithContent: ड्रॉइंग का क्रम चुनें

Modifier.drawWithContent की मदद से, कंपोज़ेबल के कॉन्टेंट से पहले या बाद में DrawScope कार्रवाइयां की जा सकती हैं. drawContent को कॉल करना न भूलें, ताकि कंपोज़ेबल का असली कॉन्टेंट रेंडर किया जा सके. इस मॉडिफ़ायर की मदद से, यह तय किया जा सकता है कि कॉन्टेंट को कस्टम ड्रॉइंग ऑपरेशन से पहले या बाद में ड्रॉ किया जाए.

उदाहरण के लिए, अगर आपको यूज़र इंटरफ़ेस (यूआई) पर फ़्लैशलाइट कीहोल इफ़ेक्ट बनाने के लिए, अपने कॉन्टेंट के ऊपर रेडियल ग्रेडिएंट रेंडर करना है, तो यह तरीका अपनाएं:

var pointerOffset by remember {
    mutableStateOf(Offset(0f, 0f))
}
Column(
    modifier = Modifier
        .fillMaxSize()
        .pointerInput("dragging") {
            detectDragGestures { change, dragAmount ->
                pointerOffset += dragAmount
            }
        }
        .onSizeChanged {
            pointerOffset = Offset(it.width / 2f, it.height / 2f)
        }
        .drawWithContent {
            drawContent()
            // draws a fully black area with a small keyhole at pointerOffset that’ll show part of the UI.
            drawRect(
                Brush.radialGradient(
                    listOf(Color.Transparent, Color.Black),
                    center = pointerOffset,
                    radius = 100.dp.toPx(),
                )
            )
        }
) {
    // Your composables here
}

पहली इमेज: फ़्लैशलाइट जैसा यूज़र इंटरफ़ेस (यूआई) बनाने के लिए, कंपोज़ेबल के ऊपर Modifier.drawWithContent का इस्तेमाल किया गया है.

Modifier.drawBehind: कंपोज़ेबल के पीछे ड्राइंग

Modifier.drawBehind की मदद से, स्क्रीन पर दिखाए गए कंपोज़ेबल कॉन्टेंट के पीछे DrawScope कार्रवाइयां की जा सकती हैं. अगर Canvas को लागू करने के तरीके पर नज़र डालें, तो आपको पता चलेगा कि यह Modifier.drawBehind के लिए सिर्फ़ एक सुविधाजनक रैपर है.

Text के पीछे गोलाकार कोनों वाला आयत बनाने के लिए:

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawBehind {
            drawRoundRect(
                Color(0xFFBBAAEE),
                cornerRadius = CornerRadius(10.dp.toPx())
            )
        }
        .padding(4.dp)
)

इससे यह नतीजा मिलता है:

Modifier.drawBehind का इस्तेमाल करके बनाया गया टेक्स्ट और बैकग्राउंड
दूसरी इमेज: Modifier.drawBehind का इस्तेमाल करके बनाया गया टेक्स्ट और बैकग्राउंड

Modifier.drawWithCache: ड्रॉइंग और कैश मेमोरी में ड्रॉ ऑब्जेक्ट सेव करना

Modifier.drawWithCache, इसके अंदर बनाए गए ऑब्जेक्ट को कैश मेमोरी में सेव रखता है. ऑब्जेक्ट तब तक कैश मेमोरी में सेव रहते हैं, जब तक ड्राइंग एरिया का साइज़ एक जैसा रहता है या पढ़े गए किसी भी स्टेट ऑब्जेक्ट में बदलाव नहीं होता. यह मॉडिफ़ायर, ड्रॉइंग कॉल की परफ़ॉर्मेंस को बेहतर बनाने के लिए काम का है. इसकी वजह यह है कि यह ड्रॉ पर बनाए गए ऑब्जेक्ट (जैसे: Brush, Shader, Path वगैरह) को फिर से असाइन करने की ज़रूरत को खत्म करता है.

इसके अलावा, मॉडिफ़ायर के बाहर remember का इस्तेमाल करके भी ऑब्जेक्ट को कैश किया जा सकता है. हालांकि, ऐसा हमेशा नहीं हो पाता, क्योंकि आपके पास कंपोज़िशन का ऐक्सेस हमेशा नहीं होता. अगर ऑब्जेक्ट का इस्तेमाल सिर्फ़ ड्राइंग के लिए किया जाता है, तो drawWithCache का इस्तेमाल करना ज़्यादा बेहतर हो सकता है.

उदाहरण के लिए, अगर आपको Text के पीछे ग्रेडिएंट बनाना है, तो drawWithCache का इस्तेमाल करके Brush ऑब्जेक्ट को तब तक कैश किया जाता है, जब तक ड्राइंग एरिया का साइज़ नहीं बदल जाता:Brush

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawWithCache {
            val brush = Brush.linearGradient(
                listOf(
                    Color(0xFF9E82F0),
                    Color(0xFF42A5F5)
                )
            )
            onDrawBehind {
                drawRoundRect(
                    brush,
                    cornerRadius = CornerRadius(10.dp.toPx())
                )
            }
        }
)

drawWithCache की मदद से, Brush ऑब्जेक्ट को कैश मेमोरी में सेव करना
तीसरी इमेज: drawWithCache की मदद से, Brush ऑब्जेक्ट को कैश मेमोरी में सेव करना

ग्राफ़िक्स मॉडिफ़ायर

Modifier.graphicsLayer: कंपोज़ेबल पर ट्रांसफ़ॉर्मेशन लागू करना

Modifier.graphicsLayer एक मॉडिफ़ायर है. यह कंपोज़ेबल के कॉन्टेंट को ड्रॉ लेयर में बदल देता है. लेयर में कई तरह के फ़ंक्शन होते हैं. जैसे:

  • ड्रॉइंग के निर्देशों को अलग करना (RenderNode की तरह). लेयर के हिस्से के तौर पर कैप्चर किए गए ड्रॉइंग के निर्देशों को रेंडरिंग पाइपलाइन, ऐप्लिकेशन कोड को फिर से लागू किए बिना ही फिर से जारी कर सकती है.
  • ऐसे बदलाव जो लेयर में मौजूद सभी ड्राइंग निर्देशों पर लागू होते हैं.
  • कंपोज़िशन की सुविधाओं के लिए रास्टराइज़ेशन. किसी लेयर को रास्टर में बदलने पर, उसके ड्रॉइंग निर्देशों को लागू किया जाता है. साथ ही, आउटपुट को ऑफ़स्क्रीन बफ़र में कैप्चर किया जाता है. बाद के फ़्रेम के लिए इस तरह के बफ़र की कंपोज़िटिंग, अलग-अलग निर्देशों को लागू करने से ज़्यादा तेज़ होती है. हालांकि, स्केलिंग या रोटेशन जैसे ट्रांसफ़ॉर्म लागू होने पर, यह बिटमैप की तरह काम करेगा.

ट्रांसफ़ॉर्मेशन

Modifier.graphicsLayer, ड्रॉइंग के निर्देशों को अलग करता है. उदाहरण के लिए, Modifier.graphicsLayer का इस्तेमाल करके अलग-अलग ट्रांसफ़ॉर्मेशन लागू किए जा सकते हैं. इनमें ऐनिमेशन जोड़ा जा सकता है या बदलाव किया जा सकता है. इसके लिए, ड्रॉइंग लैम्ब्डा को फिर से लागू करने की ज़रूरत नहीं होती.

Modifier.graphicsLayer से, आपके कंपोज़ेबल के मेज़र किए गए साइज़ या प्लेसमेंट में कोई बदलाव नहीं होता, क्योंकि इससे सिर्फ़ ड्रॉ फ़ेज़ पर असर पड़ता है. इसका मतलब है कि अगर आपका कंपोज़ेबल, लेआउट की सीमाओं से बाहर चला जाता है, तो हो सकता है कि वह दूसरों के साथ ओवरलैप हो जाए.

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

स्केल - साइज़ बढ़ाएं

scaleX और scaleY, कॉन्टेंट को हॉरिज़ॉन्टल या वर्टिकल दिशा में बड़ा या छोटा करते हैं. 1.0f वैल्यू का मतलब है कि स्केल में कोई बदलाव नहीं हुआ है. वहीं, 0.5f वैल्यू का मतलब है कि डाइमेंशन आधा है.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.scaleX = 1.2f
            this.scaleY = 0.8f
        }
)

चौथी इमेज: Image कंपोज़ेबल पर scaleX और scaleY लागू किया गया है
अनुवाद

translationX और translationY को graphicsLayer से बदला जा सकता है. translationX, कंपोज़ेबल को बाईं या दाईं ओर ले जाता है. translationY कंपोज़ेबल को ऊपर या नीचे ले जाता है.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.translationX = 100.dp.toPx()
            this.translationY = 10.dp.toPx()
        }
)

पांचवीं इमेज: Modifier.graphicsLayer का इस्तेमाल करके, इमेज पर translationX और translationY लागू किया गया है
रोटेशन

हॉरिजॉन्टल तरीके से घुमाने के लिए rotationX, वर्टिकल तरीके से घुमाने के लिए rotationY, और Z ऐक्सिस पर घुमाने के लिए (स्टैंडर्ड रोटेशन) rotationZ सेट करें. यह वैल्यू डिग्री (0-360) में दी जाती है.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

छठी इमेज: Modifier.graphicsLayer की मदद से, इमेज पर rotationX, rotationY, और rotationZ सेट किया गया है
Origin

transformOrigin तय किया जा सकता है. इसके बाद, इसका इस्तेमाल उस पॉइंट के तौर पर किया जाता है जहां से बदलाव किए जाते हैं. अब तक के सभी उदाहरणों में TransformOrigin.Center का इस्तेमाल किया गया है, जो (0.5f, 0.5f) पर है. अगर आपने ऑरिजिन को (0f, 0f) पर सेट किया है, तो ट्रांसफ़ॉर्मेशन, कंपोज़ेबल के सबसे ऊपर बाएं कोने से शुरू होंगे.

rotationZ ट्रांसफ़ॉर्मेशन का इस्तेमाल करके ऑरिजिन बदलने पर, आपको दिखेगा कि आइटम, कंपोज़ेबल के ऊपर बाईं ओर घूम रहा है:

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.transformOrigin = TransformOrigin(0f, 0f)
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

सातवीं इमेज: TransformOrigin को 0f, 0f पर सेट करके रोटेशन लागू किया गया है

क्लिप और आकार

शेप, उस आउटलाइन के बारे में बताता है जिसमें कॉन्टेंट तब क्लिप होता है, जब clip = true. इस उदाहरण में, हमने दो बॉक्स में दो अलग-अलग क्लिप सेट की हैं. इनमें से एक में graphicsLayer क्लिप वैरिएबल का इस्तेमाल किया गया है और दूसरे में Modifier.clip रैपर का इस्तेमाल किया गया है.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .size(200.dp)
            .graphicsLayer {
                clip = true
                shape = CircleShape
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }
    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(CircleShape)
            .background(Color(0xFF4DB6AC))
    )
}

पहले बॉक्स (जिसमें “Hello Compose” लिखा है) के कॉन्टेंट को सर्कल के आकार में काटा गया है:

क्लिप को Box कंपोज़ेबल पर लागू किया गया
आठवीं इमेज: Box कंपोज़ेबल पर क्लिप लागू की गई है

इसके बाद, अगर आपने सबसे ऊपर मौजूद गुलाबी सर्कल पर translationY लागू किया है, तो आपको दिखेगा कि कंपोज़ेबल की सीमाएं अब भी वही हैं. हालांकि, सर्कल को सबसे नीचे मौजूद सर्कल के नीचे (और उसकी सीमाओं के बाहर) बनाया गया है.

अनुवादY के साथ लागू की गई क्लिप और आउटलाइन के लिए लाल बॉर्डर
नौवीं इमेज: क्लिप पर translationY लागू किया गया है. साथ ही, आउटलाइन के लिए लाल रंग का बॉर्डर इस्तेमाल किया गया है

कंपोज़ेबल को उस क्षेत्र में क्लिप करने के लिए जिसमें उसे बनाया गया है, मॉडिफ़ायर चेन की शुरुआत में एक और Modifier.clip(RectangleShape) जोड़ा जा सकता है. इसके बाद, कॉन्टेंट ओरिजनल बाउंड्री के अंदर ही रहता है.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .clip(RectangleShape)
            .size(200.dp)
            .border(2.dp, Color.Black)
            .graphicsLayer {
                clip = true
                shape = CircleShape
                translationY = 50.dp.toPx()
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }

    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(RoundedCornerShape(500.dp))
            .background(Color(0xFF4DB6AC))
    )
}

ग्राफ़िक्स लेयर के ट्रांसफ़ॉर्मेशन के ऊपर क्लिप लागू की गई है
दसवीं इमेज: क्लिप को graphicsLayer ट्रांसफ़ॉर्मेशन के ऊपर लागू किया गया है

ऐल्फ़ा

Modifier.graphicsLayer का इस्तेमाल, पूरी लेयर के लिए alpha (अपारदर्शिता) सेट करने के लिए किया जा सकता है. 1.0f पूरी तरह से अपारदर्शी है और 0.0f नहीं दिख रहा है.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "clock",
    modifier = Modifier
        .graphicsLayer {
            this.alpha = 0.5f
        }
)

ऐल्फ़ा लेयर वाली इमेज
ग्यारहवीं इमेज: ऐल्फ़ा लागू की गई इमेज

कंपोज़िट करने की रणनीति

ऐल्फ़ा और पारदर्शिता के साथ काम करना, ऐल्फ़ा वैल्यू को बदलने जितना आसान नहीं हो सकता. अल्फ़ा बदलने के अलावा, graphicsLayer पर CompositingStrategy सेट करने का विकल्प भी होता है. CompositingStrategy यह तय करता है कि कंपोज़ेबल के कॉन्टेंट को स्क्रीन पर पहले से मौजूद अन्य कॉन्टेंट के साथ कैसे कंपोज़ (एक साथ रखा) किया जाए.

अलग-अलग रणनीतियां ये हैं:

अपने-आप चालू और बंद (डिफ़ॉल्ट)

कंपोज़िटिंग की रणनीति, बाकी graphicsLayer पैरामीटर से तय होती है. अगर ऐल्फ़ा की वैल्यू 1.0f से कम है या RenderEffect सेट है, तो यह लेयर को ऑफ़स्क्रीन बफ़र में रेंडर करता है. जब भी ऐल्फ़ा 1f से कम होता है, तब कॉन्टेंट को रेंडर करने के लिए कंपोज़िटिंग लेयर अपने-आप बन जाती है. इसके बाद, इस ऑफ़स्क्रीन बफ़र को डेस्टिनेशन पर, उससे जुड़े ऐल्फ़ा के साथ ड्रा किया जाता है. RenderEffect या ओवरस्क्रोल सेट करने पर, कॉन्टेंट हमेशा स्क्रीन से बाहर मौजूद बफ़र में रेंडर होता है. भले ही, CompositingStrategy सेट किया गया हो.

ऑफ़स्क्रीन

कंपोज़ेबल के कॉन्टेंट को डेस्टिनेशन पर रेंडर करने से पहले, उसे हमेशा ऑफ़स्क्रीन टेक्सचर या बिटमैप में बदल दिया जाता है. यह कॉन्टेंट को मास्क करने के लिए, BlendMode ऑपरेशन लागू करने में मददगार होता है. साथ ही, यह ड्राइंग के मुश्किल निर्देशों के सेट को रेंडर करने के दौरान परफ़ॉर्मेंस को बेहतर बनाने में भी मदद करता है.

BlendModes के साथ CompositingStrategy.Offscreen का इस्तेमाल करने का उदाहरण यहां दिया गया है. यहां दिए गए उदाहरण में, मान लें कि आपको Image का इस्तेमाल करके, ड्रॉ कमांड जारी करके Image के कुछ हिस्सों को हटाना है.BlendMode.Clear अगर आपने compositingStrategy को CompositingStrategy.Offscreen पर सेट नहीं किया है, तो BlendMode इसके नीचे मौजूद सभी कॉन्टेंट के साथ इंटरैक्ट करता है.

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = "Dog",
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(120.dp)
        .aspectRatio(1f)
        .background(
            Brush.linearGradient(
                listOf(
                    Color(0xFFC5E1A5),
                    Color(0xFF80DEEA)
                )
            )
        )
        .padding(8.dp)
        .graphicsLayer {
            compositingStrategy = CompositingStrategy.Offscreen
        }
        .drawWithCache {
            val path = Path()
            path.addOval(
                Rect(
                    topLeft = Offset.Zero,
                    bottomRight = Offset(size.width, size.height)
                )
            )
            onDrawWithContent {
                clipPath(path) {
                    // this draws the actual image - if you don't call drawContent, it wont
                    // render anything
                    this@onDrawWithContent.drawContent()
                }
                val dotSize = size.width / 8f
                // Clip a white border for the content
                drawCircle(
                    Color.Black,
                    radius = dotSize,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    ),
                    blendMode = BlendMode.Clear
                )
                // draw the red circle indication
                drawCircle(
                    Color(0xFFEF5350), radius = dotSize * 0.8f,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    )
                )
            }
        }
)

CompositingStrategy को Offscreen पर सेट करने से, एक ऑफ़स्क्रीन टेक्सचर बनता है. इससे, निर्देशों को लागू किया जा सकता है. साथ ही, BlendMode को सिर्फ़ इस कंपोज़ेबल के कॉन्टेंट पर लागू किया जा सकता है. इसके बाद, यह उसे स्क्रीन पर पहले से रेंडर किए गए कॉन्टेंट के ऊपर रेंडर करता है. इससे पहले से रेंडर किए गए कॉन्टेंट पर कोई असर नहीं पड़ता.

Modifier.drawWithContent on an Image showing a circle indication, with the BlendMode.Clear inside app
बारहवीं इमेज: इमेज पर Modifier.drawWithContent दिखाया गया है. इसमें एक सर्कल इंडिकेटर है. साथ ही, ऐप्लिकेशन में BlendMode.Clear और CompositingStrategy.Offscreen दिखाया गया है

अगर आपने CompositingStrategy.Offscreen का इस्तेमाल नहीं किया है, तो BlendMode.Clear लागू करने पर, डेस्टिनेशन में मौजूद सभी पिक्सल हट जाते हैं. भले ही, पहले से क्या सेट किया गया था. इससे विंडो का रेंडरिंग बफ़र (काला) दिखने लगता है. अल्फ़ा शामिल करने वाले कई BlendModes, ऑफ़स्क्रीन बफ़र के बिना उम्मीद के मुताबिक काम नहीं करेंगे. लाल रंग के सर्कल इंडिकेटर के चारों ओर मौजूद काली रिंग पर ध्यान दें:

Modifier.drawWithContent on an Image showing a circle indication, with the BlendMode.Clear and no CompositingStrategy set
तेरहवीं इमेज: एक इमेज पर Modifier.drawWithContent दिखाया गया है. इसमें BlendMode.Clear का इस्तेमाल किया गया है और CompositingStrategy सेट नहीं किया गया है. साथ ही, इसमें एक सर्कल इंडिकेटर दिखाया गया है

इसे और बेहतर तरीके से समझने के लिए: अगर ऐप्लिकेशन में पारदर्शी विंडो बैकग्राउंड है और आपने CompositingStrategy.Offscreen का इस्तेमाल नहीं किया है, तो CompositingStrategy.Offscreen पूरे ऐप्लिकेशन के साथ इंटरैक्ट करेगा. यह सभी पिक्सल को मिटा देगा, ताकि नीचे मौजूद ऐप्लिकेशन या वॉलपेपर दिख सके. उदाहरण के लिए:BlendMode

कोई CompositingStrategy सेट नहीं की गई है और BlendMode.Clear का इस्तेमाल किया जा रहा है. साथ ही, ऐप्लिकेशन में ट्रांसलूसेंट विंडो बैकग्राउंड है. लाल रंग के स्टेटस सर्कल के आस-पास, गुलाबी रंग का वॉलपेपर दिख रहा है.
चौथी इमेज: CompositingStrategy सेट नहीं की गई है. साथ ही, BlendMode.Clear का इस्तेमाल किया जा रहा है. इस ऐप्लिकेशन की विंडो का बैकग्राउंड हल्का पारदर्शी है. ध्यान दें कि लाल रंग के स्टेटस सर्कल के आस-पास के हिस्से में, गुलाबी रंग का वॉलपेपर कैसे दिख रहा है.

ध्यान दें कि CompositingStrategy.Offscreen का इस्तेमाल करने पर, ड्राइंग एरिया के साइज़ का एक ऑफ़स्क्रीन टेक्सचर बनाया जाता है. इसके बाद, इसे स्क्रीन पर रेंडर किया जाता है. इस रणनीति का इस्तेमाल करके की गई कोई भी ड्रॉइंग कमांड, डिफ़ॉल्ट रूप से इस क्षेत्र के हिसाब से काटी जाती है. नीचे दिए गए कोड स्निपेट में, ऑफ़स्क्रीन टेक्सचर का इस्तेमाल करने पर होने वाले अंतर के बारे में बताया गया है:

@Composable
fun CompositingStrategyExamples() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize(Alignment.Center)
    ) {
        // Does not clip content even with a graphics layer usage here. By default, graphicsLayer
        // does not allocate + rasterize content into a separate layer but instead is used
        // for isolation. That is draw invalidations made outside of this graphicsLayer will not
        // re-record the drawing instructions in this composable as they have not changed
        Canvas(
            modifier = Modifier
                .graphicsLayer()
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            // ... and drawing a size of 200 dp here outside the bounds
            drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }

        Spacer(modifier = Modifier.size(300.dp))

        /* Clips content as alpha usage here creates an offscreen buffer to rasterize content
        into first then draws to the original destination */
        Canvas(
            modifier = Modifier
                // force to an offscreen buffer
                .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the
            content gets clipped */
            drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }
    }
}

CompositingStrategy.Auto बनाम CompositingStrategy.Offscreen - offscreen, उस क्षेत्र में क्लिप करता है जहां auto ऐसा नहीं करता
पंद्रहवीं इमेज: CompositingStrategy.Auto बनाम CompositingStrategy.Offscreen - ऑफ़स्क्रीन क्लिप को उस क्षेत्र में ले जाएं जहां ऑटो काम नहीं करता
ModulateAlpha

इस कंपोज़िशन की रणनीति से, graphicsLayer में रिकॉर्ड किए गए हर ड्रॉइंग निर्देश के लिए ऐल्फ़ा को मॉड्युलेट किया जाता है. यह 1.0f से कम ऐल्फ़ा के लिए, ऑफ़स्क्रीन बफ़र नहीं बनाता है. हालांकि, अगर RenderEffect सेट किया जाता है, तो यह ऐल्फ़ा रेंडरिंग के लिए ज़्यादा असरदार हो सकता है. हालांकि, एक जैसे कॉन्टेंट के लिए, यह अलग-अलग नतीजे दे सकता है. जिन मामलों में यह पहले से पता होता है कि कॉन्टेंट ओवरलैप नहीं हो रहा है वहां यह, CompositingStrategy.Auto की तुलना में बेहतर परफ़ॉर्मेंस दे सकता है. हालांकि, ऐसा तब होता है, जब ऐल्फ़ा वैल्यू 1 से कम हो.

कंपोज़िशन की अलग-अलग रणनीतियों का एक और उदाहरण यहां दिया गया है. इसमें कंपोज़ेबल के अलग-अलग हिस्सों पर अलग-अलग ऐल्फ़ा लागू किए गए हैं. साथ ही, Modulate रणनीति लागू की गई है:

@Preview
@Composable
fun CompositingStrategy_ModulateAlpha() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(32.dp)
    ) {
        // Base drawing, no alpha applied
        Canvas(
            modifier = Modifier.size(200.dp)
        ) {
            drawSquares()
        }

        Spacer(modifier = Modifier.size(36.dp))

        // Alpha 0.5f applied to whole composable
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    alpha = 0.5f
                }
        ) {
            drawSquares()
        }
        Spacer(modifier = Modifier.size(36.dp))

        // 0.75f alpha applied to each draw call when using ModulateAlpha
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    compositingStrategy = CompositingStrategy.ModulateAlpha
                    alpha = 0.75f
                }
        ) {
            drawSquares()
        }
    }
}

private fun DrawScope.drawSquares() {

    val size = Size(100.dp.toPx(), 100.dp.toPx())
    drawRect(color = Red, size = size)
    drawRect(
        color = Purple, size = size,
        topLeft = Offset(size.width / 4f, size.height / 4f)
    )
    drawRect(
        color = Yellow, size = size,
        topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f)
    )
}

val Purple = Color(0xFF7E57C2)
val Yellow = Color(0xFFFFCA28)
val Red = Color(0xFFEF5350)

ModulateAlpha, हर ड्रॉ कमांड के लिए सेट किए गए ऐल्फ़ा को लागू करता है
सोलहवीं इमेज: ModulateAlpha, हर ड्रॉ कमांड पर सेट किए गए ऐल्फ़ा को लागू करता है

किसी कंपोज़ेबल के कॉन्टेंट को बिटमैप में लिखना

आम तौर पर, कंपोज़ेबल से Bitmap बनाया जाता है. अपने कंपोज़ेबल के कॉन्टेंट को Bitmap में कॉपी करने के लिए, rememberGraphicsLayer() का इस्तेमाल करके GraphicsLayer बनाएं.

drawWithContent() और graphicsLayer.record{} का इस्तेमाल करके, ड्राइंग के निर्देशों को नई लेयर पर रीडायरेक्ट करें. इसके बाद, दिखने वाले कैनवस में लेयर बनाएं. इसके लिए, drawLayer का इस्तेमाल करें:

val coroutineScope = rememberCoroutineScope()
val graphicsLayer = rememberGraphicsLayer()
Box(
    modifier = Modifier
        .drawWithContent {
            // call record to capture the content in the graphics layer
            graphicsLayer.record {
                // draw the contents of the composable into the graphics layer
                this@drawWithContent.drawContent()
            }
            // draw the graphics layer on the visible canvas
            drawLayer(graphicsLayer)
        }
        .clickable {
            coroutineScope.launch {
                val bitmap = graphicsLayer.toImageBitmap()
                // do something with the newly acquired bitmap
            }
        }
        .background(Color.White)
) {
    Text("Hello Android", fontSize = 26.sp)
}

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

कस्टम ड्रॉइंग मॉडिफ़ायर

अपनी पसंद के मुताबिक मॉडिफ़ायर बनाने के लिए, DrawModifier इंटरफ़ेस लागू करें. इससे आपको ContentDrawScope का ऐक्सेस मिलता है. यह Modifier.drawWithContent() का इस्तेमाल करते समय दिखने वाले ContentDrawScope जैसा ही होता है. इसके बाद, कोड को बेहतर बनाने और रैपर उपलब्ध कराने के लिए, सामान्य ड्राइंग ऑपरेशन को कस्टम ड्राइंग मॉडिफ़ायर में बदला जा सकता है. उदाहरण के लिए, Modifier.background() एक सुविधाजनक DrawModifier है.

उदाहरण के लिए, अगर आपको ऐसा Modifier लागू करना है जो कॉन्टेंट को वर्टिकल तौर पर फ़्लिप करता है, तो इसे इस तरह बनाया जा सकता है:

class FlippedModifier : DrawModifier {
    override fun ContentDrawScope.draw() {
        scale(1f, -1f) {
            this@draw.drawContent()
        }
    }
}

fun Modifier.flipped() = this.then(FlippedModifier())

इसके बाद, Text पर लागू किए गए इस फ़्लिप्ड मॉडिफ़ायर का इस्तेमाल करें:

Text(
    "Hello Compose!",
    modifier = Modifier
        .flipped()
)

टेक्स्ट पर कस्टम फ़्लिप्ड मॉडिफ़ायर
17वीं इमेज: टेक्स्ट पर कस्टम फ़्लिप्ड मॉडिफ़ायर

अन्य संसाधन

graphicsLayer और कस्टम ड्राइंग का इस्तेमाल करने के बारे में ज़्यादा उदाहरण देखने के लिए, यहां दिए गए लेख पढ़ें: