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

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

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

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

ड्रॉइंग के लिए बुनियादी मॉडिफ़ायर drawWithContent है. यहां आपके पास अपने Composable के ड्रॉइंग क्रम और मॉडिफ़ायर में दिए गए ड्रॉइंग निर्देशों को तय करने का विकल्प होता है. 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
}

पहली इमेज: फ़्लैशलाइट टाइप का यूज़र इंटरफ़ेस (यूआई) अनुभव देने के लिए, Composable के ऊपर 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 के पीछे ग्रेडिएंट बनाने के लिए Brush बनाया जाता है, तो drawWithCache का इस्तेमाल करने पर Brush ऑब्जेक्ट को तब तक कैश मेमोरी में सेव रखा जाता है, जब तक कि ड्रॉइंग एरिया का साइज़ नहीं बदलता:

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

ड्रॉर के साथ ब्रश ऑब्जेक्ट को कैश मेमोरी में सेव करना
तीसरी इमेज: ड्रॉर के ऑब्जेक्ट को Drawings के साथ कैश मेमोरी में सेव करना

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

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
        }
)

चौथी इमेज: इमेज कॉम्पोज़ेबल पर 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
        }
)

इमेज 7: रोटेशन लागू किया गया है और 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 कंपोज़ेबल पर क्लिप लागू की गई
आठवां इलस्ट्रेशन: बॉक्स कॉम्पोज़ेबल पर लागू की गई क्लिप

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

अनुवाद के साथ लागू की गई क्लिप और आउटलाइन के लिए लाल बॉर्डर
चित्र 9: अनुवाद के साथ लागू की गई क्लिप और आउटलाइन के लिए लाल बॉर्डर

जिस क्षेत्र में कॉम्पोज़ेबल बनाया गया है उसी क्षेत्र में उसे क्लिप करने के लिए, मॉडिफ़ायर चेन की शुरुआत में एक और 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 ट्रांसफ़ॉर्मेशन के ऊपर लागू की गई क्लिप
पहली इमेज: 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 ऑपरेशन लागू करने के लिए और ड्रॉइंग के निर्देशों के जटिल सेट को रेंडर करते समय परफ़ॉर्मेंस के लिए मददगार है.

CompositingStrategy.Offscreen का इस्तेमाल करने का एक उदाहरण, BlendModes के साथ है. नीचे दिए गए उदाहरण पर गौर करें. मान लें कि आपको BlendMode.Clear का इस्तेमाल करने वाले ड्रॉ कमांड का इस्तेमाल करके, Image कॉम्पोज़ेबल के कुछ हिस्सों को हटाना है. अगर 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 को इस कंपोज़ेबल के कॉन्टेंट पर लागू किया जा सकता है. इसके बाद, इसे स्क्रीन पर पहले से रेंडर किए गए कॉन्टेंट के ऊपर रेंडर किया जाता है. इससे पहले से रेंडर किए गए कॉन्टेंट पर कोई असर नहीं पड़ता.

ऐप्लिकेशन में BlendMode.Clear के साथ, सर्कल का संकेत दिखाने वाली इमेज पर Modifier.drawWithContent
इमेज पर Modifier.drawWithContent का इस्तेमाल करके, ऐप्लिकेशन में BlendMode.Clear और CompositingStrategy.Offscreen की मदद से सर्कल का इंंडिकेशन दिखाना

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

Modifier.drawWithContent का इस्तेमाल करके, इमेज पर सर्कल का निशान दिखाया गया है. इसमें BlendMode.Clear का इस्तेमाल किया गया है और CompositingStrategy सेट नहीं की गई है
इमेज पर Modifier.drawWithContent का इस्तेमाल करके, सर्कल का निशान दिखाया गया है. इसमें BlendMode.Clear का इस्तेमाल किया गया है और कोई CompositingStrategy सेट नहीं की गई है

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

CompositingStrategy सेट नहीं है और किसी ऐसे ऐप्लिकेशन के साथ BlendMode.Clear का इस्तेमाल किया जा रहा है जिसकी विंडो का बैकग्राउंड पारदर्शी है. गुलाबी वॉलपेपर, लाल रंग के स्टेटस सर्कल के आस-पास के हिस्से में दिखता है.
इमेज 14: 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 - उस क्षेत्र में ऑफ़स्क्रीन क्लिप जहां ऑटो नहीं करता
15वीं इमेज: 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() का इस्तेमाल करने पर दिखाया जाता है. इसके बाद, कोड को बेहतर बनाने और आसान व्रपर उपलब्ध कराने के लिए, कस्टम ड्रॉइंग मॉडिफ़ायर में ड्रॉइंग के सामान्य ऑपरेशन निकाले जा सकते हैं. उदाहरण के लिए, 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 और कस्टम ड्रॉइंग का इस्तेमाल करने वाले ज़्यादा उदाहरणों के लिए, नीचे दिए गए संसाधन देखें: