গ্রাফিক্স মডিফায়ার

Canvas কম্পোজেবল ছাড়াও, Compose-এ বেশ কিছু উপযোগী গ্রাফিক্স Modifiers আছে যা কাস্টম কন্টেন্ট আঁকতে সাহায্য করে। এই মডিফায়ারগুলি উপযোগী কারণ এগুলি যেকোনও কম্পোজেবলে প্রয়োগ করা যেতে পারে।

ড্রয়িং মডিফায়ার

Compose-এ ড্রয়িং মডিফায়ারের মাধ্যমে সব ড্রয়িং কমান্ড করা হয়। Compose-এ তিনটি প্রধান ড্রয়িং মডিফায়ার আছে:

ড্রয়িংয়ের জন্য বেস মডিফায়ার হল drawWithContent, যেখানে আপনি কম্পোজেবলের ড্রয়িং অর্ডার এবং মডিফায়ারের মধ্যে ইস্যু করা ড্রয়িং কমান্ডের ব্যাপারে সিদ্ধান্ত নিতে পারবেন। drawBehind হল drawWithContent-এর চারপাশে একটি সুবিধাজনক র‍্যাপার, যার ড্রয়িং অর্ডার কম্পোজেবলের কন্টেন্টের পিছনে সেট করা আছে। drawWithCache তার মধ্যে onDrawBehind বা onDrawWithContent-কে কল করে - এবং সেগুলিতে তৈরি করা অবজেক্ট ক্যাশে করার জন্য একটি মেকানিজম প্রদান করে।

Modifier.drawWithContent: ড্রয়িংয়ের ক্রম বেছে নিন

Modifier.drawWithContent আপনাকে কম্পোজেবলের কন্টেন্টের আগে বা পরে DrawScope অপারেশন এক্সিকিউট করতে দেয়। কম্পোজেবলের আসল কন্টেন্ট রেন্ডার করার জন্য drawContent কল করতে ভুলবেন না। এই মডিফায়ারের সাহায্যে, আপনি অপারেশনের ক্রম নির্ধারণ করতে পারবেন, যদি আপনি চান যে আপনার কন্টেন্ট আপনার কাস্টম ড্রয়িং অপারেশনের আগে বা পরে আঁকা হোক।

যেমন, UI-তে ফ্ল্যাশলাইট কীহোল এফেক্ট তৈরি করার জন্য আপনার কন্টেন্টের উপরে রেডিয়াল গ্রেডিয়েন্ট রেন্ডার করতে চাইলে, আপনি নিম্নলিখিত কাজ করতে পারেন:

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
}

ছবি ১: ফ্ল্যাশলাইট টাইপের UI অভিজ্ঞতা তৈরি করতে 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())
                )
            }
        }
)

drawWithCache-এর মাধ্যমে ব্রাশ অবজেক্ট ক্যাশে করা হচ্ছে
ছবি ৩: drawWithCache-এর সাহায্যে ব্রাশ অবজেক্ট ক্যাশ করা

গ্রাফিক্স মডিফায়ার

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 সেট করুন। এই ভ্যালু ডিগ্রিতে (০-৩৬০) নির্দিষ্ট করা হয়।

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 সেট করা হয়েছে
উৎস

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 composable-এ ক্লিপ প্রয়োগ করা হয়েছে
ছবি ৮: বক্স কম্পোজেবলে ক্লিপ প্রয়োগ করা হয়েছে

এরপর আপনি যদি উপরের গোলাপি বৃত্তে translationY প্রয়োগ করেন, তাহলে দেখতে পাবেন যে Composable-এর বাউন্ড এখনও একই আছে, কিন্তু বৃত্তটি নিচের বৃত্তের নিচে (এবং এর বাউন্ডের বাইরে) আঁকা হয়েছে।

translationY সহ ক্লিপ প্রয়োগ করা হয়েছে এবং আউটলাইনের জন্য লাল বর্ডার ব্যবহার করা হয়েছে
ছবি ৯: 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 ট্রান্সফর্মেশনের উপরে ক্লিপ প্রয়োগ করা হয়েছে
ছবি ১০: graphicsLayer ট্রান্সফর্মেশনের উপরে ক্লিপ প্রয়োগ করা হয়েছে

আলফা

পুরো লেয়ারের জন্য alpha (অস্বচ্ছতা) সেট করতে Modifier.graphicsLayer ব্যবহার করা যেতে পারে। 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-এর কম হলে, কন্টেন্ট রেন্ডার করার জন্য অটোমেটিক কম্পোজিটিং লেয়ার তৈরি হয় এবং তারপর এই অফস্ক্রিন বাফারকে সংশ্লিষ্ট আলফা সহ ডেস্টিনেশনে ড্র করে। CompositingStrategy সেট করা যাই হোক না কেন, RenderEffect বা ওভারস্ক্রল সেট করলে, কন্টেন্ট সবসময় অফস্ক্রিন বাফারে রেন্ডার হয়।

স্ক্রিনের বাইরে

কম্পোজেবলের কন্টেন্ট গন্তব্যে রেন্ডার করার আগে সবসময় অফস্ক্রিন টেক্সচার বা বিটম্যাপে রাস্টারাইজ করা হয়। এটি কন্টেন্ট মাস্ক করার জন্য BlendMode অপারেশন প্রয়োগ করতে এবং জটিল সেট আঁকার নির্দেশাবলী রেন্ডার করার সময় পারফর্ম্যান্সের জন্য সহায়ক।

BlendModes-এর সাথে CompositingStrategy.Offscreen ব্যবহার করার একটি উদাহরণ। নিচের উদাহরণে, ধরুন আপনি 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
ছবি ১২: অ্যাপের মধ্যে BlendMode.Clear এবং CompositingStrategy.Offscreen সহ বৃত্তাকার ইঙ্গিত দেখানো ছবির উপর Modifier.drawWithContent

আপনি CompositingStrategy.Offscreen ব্যবহার না করলে, BlendMode.Clear প্রয়োগ করার ফলাফল গন্তব্যের সব পিক্সেল মুছে দেয়, আগে থেকে কী সেট করা ছিল তা নির্বিশেষে - উইন্ডোর রেন্ডারিং বাফার (কালো) দৃশ্যমান রেখে। আলফা সহ BlendModes-এর অনেকগুলি অফস্ক্রিন বাফার ছাড়া প্রত্যাশিতভাবে কাজ করবে না। লাল বৃত্তাকার ইন্ডিকেটরের চারপাশে কালো রিংটি লক্ষ্য করুন:

BlendMode.Clear এবং কোনও CompositingStrategy সেট না করে, বৃত্তের ইঙ্গিত দেখানো কোনও ছবিতে Modifier.drawWithContent
ছবি ১৩: BlendMode.Clear এবং কোনও CompositingStrategy সেট না করে, বৃত্তাকার ইঙ্গিত দেখানো কোনও ছবিতে Modifier.drawWithContent

এটি আরও ভালভাবে বুঝতে: অ্যাপের উইন্ডোর ব্যাকগ্রাউন্ড যদি স্বচ্ছ হয় এবং আপনি 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 - অফস্ক্রিন ক্লিপগুলি অঞ্চলে, যেখানে অটো নেই
ছবি ১৫: CompositingStrategy.Auto বনাম CompositingStrategy.Offscreen - অফস্ক্রিন ক্লিপগুলি অঞ্চলে, যেখানে অটো নেই
ModulateAlpha

এই কম্পোজিশন স্ট্র্যাটেজি graphicsLayer-এর মধ্যে রেকর্ড করা প্রতিটি ড্রয়িং নির্দেশের জন্য আলফা মডিউলেট করে। RenderEffect সেট না করা থাকলে, এটি ১.০f-এর কম আলফার জন্য অফস্ক্রিন বাফার তৈরি করবে না, তাই এটি আলফা রেন্ডারিংয়ের জন্য আরও দক্ষ হতে পারে। তবে, ওভারল্যাপ করা কন্টেন্টের জন্য এটি আলাদা ফলাফল দেখাতে পারে। যেসব ক্ষেত্রে আগে থেকেই জানা থাকে যে কন্টেন্ট ওভারল্যাপ করছে না, সেখানে এটি CompositingStrategy.Auto-এর থেকে ভাল পারফর্ম্যান্স দিতে পারে, যেখানে আলফা ভ্যালু ১-এর কম।

নিম্নলিখিত উদাহরণে বিভিন্ন কম্পোজিশন স্ট্র্যাটেজি দেখানো হয়েছে - কম্পোজেবলের বিভিন্ন অংশে বিভিন্ন আলফা প্রয়োগ করা এবং 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)
    )
}

@Preview
@Composable
fun ModifierGraphicsLayerOutsets() {
    Column(
        modifier = Modifier
            .background(Color(0xFF0A0E14))
            .padding(48.dp),
        verticalArrangement = Arrangement.spacedBy(48.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        val pillShape = RoundedCornerShape(percent = 50)

        // Without outsets: alpha = 0.5f creates an offscreen buffer that clips the 24.dp glow
        Box(
            modifier = Modifier
                .size(
                    width = 140.dp,
                    height = 56.dp
                )
                .graphicsLayer(
                    alpha = 0.5f,
                    clip = false
                )
                .dropShadow(
                    shape = pillShape,
                    shadow = Shadow(radius = 24.dp, color = Color.Cyan)
                )
                .background(color = Color(0xFF141824), shape = pillShape),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "✨ Glow",
                color = Color.White,
                fontSize = 18.sp
            )
        }

        Box(
            modifier = Modifier
                .size(
                    width = 140.dp,
                    height = 56.dp
                )
                .graphicsLayer(
                    alpha = 0.5f,
                    outsets = LayerOutsets(24.dp),
                    clip = false
                )
                .dropShadow(
                    shape = pillShape,
                    shadow = Shadow(radius = 24.dp, color = Color.Cyan)
                )
                .background(color = Color(0xFF141824), shape = pillShape),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "✨ Glow",
                color = Color.White,
                fontSize = 18.sp
            )
        }
    }
}


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

ModulateAlpha প্রতিটি ড্র কমান্ডে আলফা সেট প্রয়োগ করে
ছবি ১৬: ModulateAlpha প্রতিটি আলাদা ড্র কমান্ডে আলফা সেট প্রয়োগ করে

লেয়ার আউটসেট

যখন একটি কম্পোজ করার উপযুক্ত আইটেম অফস্ক্রিন বাফারে আঁকা হয় (যেমন, যখন alpha 1.0f-এর থেকে কম হয়, একটি RenderEffect সেট করা হয়, বা CompositingStrategy.Offscreen ব্যবহার করা হয়), অফস্ক্রিন টেক্সচার কম্পোজ করার উপযুক্ত আইটেমের পরিমাপ করা লেআউট বাউন্ডে ডিফল্ট হয়। লেয়ারের মধ্যে থাকা যেকোনও ড্রয়িং বা শ্যাডো যা কম্পোজেবলের লেআউট বাউন্ডের বাইরে বিস্তৃত হয় (যেমন, Modifier.dropShadow দিয়ে আঁকা আউটার গ্লো) সেটি অফস্ক্রিন বাফারের বাউন্ডারিতে ইমপ্লিসিটভাবে ক্লিপ করা হয়।

Modifier.padding() (যা সংলগ্ন UI এলিমেন্টের লেআউট পরিমাপ শিফ্ট করে) যোগ করার পরিবর্তে, Modifier.graphicsLayer-এর outsets প্যারামিটারে LayerOutsets পাস করুন। এটি কম্পোজেবলের পরিমাপ করা লেআউট সাইজ পরিবর্তন না করেই প্রতিটি সাইডে অফস্ক্রিন বাফারের ভিজ্যুয়াল বাউন্ড বাড়ায়। মনে রাখবেন যে LayerOutsets লেয়ারের ক্লিপ শেপ বা শ্যাডো বাউন্ড প্রসারিত করে না, যা কম্পোজেবলের আসল পরিমাপ করা সাইজের উপর ভিত্তি করে থাকে (তাই আসল বাউন্ডে ক্লিপিং এড়াতে clip = false graphicsLayer-এ রাখুন):

Box(
    modifier = Modifier
        .size(
            width = 140.dp,
            height = 56.dp
        )
        .graphicsLayer(
            alpha = 0.5f,
            outsets = LayerOutsets(24.dp),
            clip = false
        )
        .dropShadow(
            shape = pillShape,
            shadow = Shadow(radius = 24.dp, color = Color.Cyan)
        )
        .background(color = Color(0xFF141824), shape = pillShape),
    contentAlignment = Alignment.Center
) {
    Text(
        text = "✨ Glow",
        color = Color.White,
        fontSize = 18.sp
    )
}

LayerOutsets (উপরে, আয়তাকার লেআউট বাউন্ডে ক্লিপ করা) ছাড়া অফস্ক্রিন লেয়ার আউটার গ্লো বনাম LayerOutsets(24.dp) (নিচে, আনক্লিপ করা) সহ অফস্ক্রিন লেয়ার আউটার গ্লোয়ের তুলনা
ছবি ১৭: LayerOutsets ছাড়া অফস্ক্রিন লেয়ারের বাইরের গ্লো (উপরে, কম্পোজেবলের আয়তক্ষেত্রাকার লেআউট বাউন্ডে ক্লিপ করা) বনাম LayerOutsets(24.dp) সহ (নিচে, আনক্লিপ করা)

বিটম্যাপে কম্পোজেবলের কন্টেন্ট লিখুন

একটি সাধারণ ব্যবহারিক প্রয়োগ হল কম্পোজেবল থেকে 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()
)

টেক্সটে কাস্টম ফ্লিপ করা মডিফায়ার
ছবি ১৭: টেক্সটে কাস্টম ফ্লিপড মডিফায়ার

অতিরিক্ত রিসোর্স

graphicsLayer ও কাস্টম ড্রয়িং ব্যবহার করার আরও উদাহরণের জন্য, নিম্নলিখিত রিসোর্সগুলি দেখুন: