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

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 অভিজ্ঞতা তৈরি করতে, কম্পোজ করার উপযুক্ত আইটেমের উপরে 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-এর মাধ্যমে 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 composable-এ 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))
    )
}

প্রথম বক্সের কন্টেন্ট ('হ্যালো কম্পোজ' লেখা টেক্সট) বৃত্তাকার শেপে ক্লিপ করা হয়েছে:

Box composable-এ ক্লিপ প্রয়োগ করা হয়েছে
ছবি ৮: বক্স কম্পোজ করার উপযুক্ত আইটেমে ক্লিপ প্রয়োগ করা হয়েছে

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

translationY সহ ক্লিপ প্রয়োগ করা হয়েছে এবং আউটলাইনের জন্য লাল বর্ডার
ছবি ৯: ট্রান্সলেশনY প্রয়োগ করা ক্লিপ এবং আউটলাইনের জন্য লাল বর্ডার

যে অঞ্চলে কম্পোজ করার মতো আইটেম ড্র করা হয়েছে, সেখানে সেটি ক্লিপ করতে, আপনি মডিফায়ার চেনের শুরুতে আরেকটি 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))
    )
}

গ্রাফিক্সলেয়ার ট্রান্সফর্মেশনের উপরে ক্লিপ প্রয়োগ করা হয়েছে
ছবি ১০: গ্রাফিক্সলেয়ার ট্রান্সফর্মেশনের উপরে ক্লিপ প্রয়োগ করা হয়েছে

আলফা

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

অফস্ক্রিন

কম্পোজ করার উপযুক্ত আইটেমের কন্টেন্ট ডেস্টিনেশনে রেন্ডার করার আগে সবসময় অফস্ক্রিন টেক্সচার বা বিটম্যাপে র‍্যাস্টারাইজ করা হয়। এটি কন্টেন্ট মাস্ক করার জন্য 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 সহ বৃত্তাকার ইঙ্গিত দেখানো একটি ছবিতে Modifier.drawWithContent এবং কোনও CompositingStrategy সেট করা নেই
ছবি ১৩: 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-এর মধ্যে রেকর্ড করা প্রতিটি ড্রয়িং নির্দেশের জন্য আলফা মডুলেট করে। ১.০f-এর থেকে কম আলফার জন্য এটি কোনও অফস্ক্রিন বাফার তৈরি করবে না, যদি না কোনও RenderEffect সেট করা থাকে, তাই আলফা রেন্ডারিংয়ের জন্য এটি আরও বেশি কার্যকর হতে পারে। তবে, ওভারল্যাপ করা কন্টেন্টের জন্য এটি আলাদা ফলাফল প্রদান করতে পারে। যেসব ব্যবহারের ক্ষেত্রে আগে থেকেই জানা আছে যে কন্টেন্ট ওভারল্যাপ করছে না, সেখানে এটি ১-এর কম আলফা ভ্যালু সহ 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 প্রতিটি স্বতন্ত্র ড্র কমান্ডে সেট করা আলফা প্রয়োগ করে

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

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 ও কাস্টম ড্রয়িং ব্যবহার করে আরও উদাহরণ পেতে, এইসব রিসোর্স দেখুন: