Compose-এ গ্রাফিক্স

অনেক অ্যাপকে স্ক্রিনে কী আঁকা হচ্ছে তা সঠিকভাবে কন্ট্রোল করতে হয়। এটি স্ক্রিনের ঠিক জায়গায় একটি বক্স বা বৃত্ত বসানোর মতো ছোট কাজ হতে পারে অথবা অনেক আলাদা আলাদা স্টাইলে গ্রাফিক এলিমেন্টের বিস্তারিত অ্যারেঞ্জমেন্ট হতে পারে।

মডিফায়ার ও DrawScope সহ সাধারণ ড্রয়িং

Compose-এ কাস্টম কিছু আঁকার মূল উপায় হল মডিফায়ার ব্যবহার করা, যেমন Modifier.drawWithContent, Modifier.drawBehind এবং Modifier.drawWithCache।

যেমন, আপনার কম্পোজ করার উপযুক্ত আইটেমের পিছনে কিছু আঁকতে, আপনি ড্রয়িং কমান্ড এক্সিকিউট করা শুরু করতে drawBehind মডিফায়ার ব্যবহার করতে পারেন:

Spacer(
    modifier = Modifier
        .fillMaxSize()
        .drawBehind {
            // this = DrawScope
        }
)

আপনার যদি শুধু এমন একটি কম্পোজ করার মতো আইটেম প্রয়োজন হয় যা ড্র করে, তাহলে আপনি Canvas কম্পোজ করার মতো আইটেম ব্যবহার করতে পারেন। Canvas কম্পোজ করার উপযুক্ত হল Modifier.drawBehind-এর আশেপাশে থাকা একটি সুবিধাজনক র‍্যাপার। আপনি অন্য যেকোনও Compose UI এলিমেন্টের মতো একই উপায়ে আপনার লেআউটে Canvas প্লেস করেন। Canvas-এর মধ্যে, আপনি এলিমেন্টের স্টাইল ও লোকেশন নিখুঁতভাবে কন্ট্রোল করে এলিমেন্ট ড্র করতে পারবেন।

সব ড্রয়িং মডিফায়ার একটি DrawScope, একটি স্কোপ করা ড্রয়িং এনভায়রনমেন্ট প্রকাশ করে যা নিজের স্টেট বজায় রাখে। এটি আপনাকে গ্রাফিক্যাল এলিমেন্টের একটি গ্রুপের জন্য প্যারামিটার সেট করতে দেয়। DrawScope-এ size-এর মতো একাধিক উপযোগী ফিল্ড থাকে, DrawScope-এর বর্তমান ডাইমেনশন নির্দিষ্ট করা Size অবজেক্ট।

কিছু আঁকতে, আপনি DrawScope-এ থাকা অনেক ড্র ফাংশনের মধ্যে একটি ব্যবহার করতে পারেন। যেমন, নিচের কোডটি স্ক্রিনের উপরের বাঁদিকের কোণে একটি আয়তক্ষেত্র আঁকে:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    drawRect(
        color = Color.Magenta,
        size = canvasQuadrantSize
    )
}

সাদা ব্যাকগ্রাউন্ডের উপর আঁকা গোলাপি রঙের আয়তক্ষেত্র যা স্ক্রিনের এক চতুর্থাংশ জায়গা জুড়ে রয়েছে
ছবি ১. কম্পোজ উইন্ডোতে Canvas ব্যবহার করে আঁকা আয়তক্ষেত্র।

বিভিন্ন ড্রয়িং মডিফায়ার সম্পর্কে আরও জানতে, গ্রাফিক্স মডিফায়ার ডকুমেন্টেশন দেখুন।

কোঅর্ডিনেট সিস্টেম

স্ক্রিনে কিছু আঁকতে, আপনার আইটেমের অফসেট (x ও y) ও সাইজ সম্পর্কে জানতে হবে। DrawScope-এ অনেক ড্র পদ্ধতির সাথে, পজিশন ও সাইজ ডিফল্ট প্যারামিটার ভ্যালু দ্বারা প্রদান করা হয়। ডিফল্ট প্যারামিটার সাধারণত ক্যানভাসের [0, 0] পয়েন্টে আইটেমটি পজিশন করে এবং একটি ডিফল্ট size প্রদান করে যা উপরের উদাহরণে দেখানো মতো সম্পূর্ণ ড্রয়িং এরিয়া পূরণ করে - আপনি দেখতে পাবেন যে আয়তক্ষেত্রটি উপরে বাঁদিকে পজিশন করা হয়েছে। আপনার আইটেমের সাইজ ও পজিশন অ্যাডজাস্ট করতে, Compose-এর কোঅর্ডিনেট সিস্টেম বুঝতে হবে।

কোঅর্ডিনেট সিস্টেমের ([0,0]) মূল বিন্দু হল ড্রয়িং এরিয়ার সবচেয়ে উপরে বাঁদিকের পিক্সেল। ডানদিকে সরানোর সাথে সাথে x বৃদ্ধি পায় এবং নিচের দিকে সরানোর সাথে সাথে y বৃদ্ধি পায়।

কোঅর্ডিনেট সিস্টেম দেখানো একটি গ্রিড, যার উপরের বাঁদিকে [0, 0] এবং নিচের ডানদিকে [width, height] দেখানো হয়েছে
ছবি ২. ড্রয়িং কোঅর্ডিনেট সিস্টেম / ড্রয়িং গ্রিড।

যেমন, আপনি যদি ক্যানভাসের উপরের ডানদিকের কোণ থেকে নিচের বাঁদিকের কোণ পর্যন্ত একটি তির্যক রেখা আঁকতে চান, তাহলে আপনি DrawScope.drawLine() ফাংশন ব্যবহার করতে পারেন এবং সংশ্লিষ্ট x ও y পজিশনের সাথে একটি শুরু ও শেষের অফসেট নির্দিষ্ট করতে পারেন:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height
    drawLine(
        start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue
    )
}

প্রাথমিক ট্রান্সফর্মেশন

DrawScope ড্রয়িং কমান্ড কোথায় বা কীভাবে এক্সিকিউট করা হবে তা পরিবর্তন করার জন্য ট্রান্সফর্মেশন অফার করে।

স্কেল

আপনার ড্রয়িং অপারেশনের সাইজ ফ্যাক্টর অনুযায়ী বাড়াতে DrawScope.scale() ব্যবহার করুন। scale()-এর মতো অপারেশন সংশ্লিষ্ট ল্যাম্বডার মধ্যে থাকা সব ড্রয়িং অপারেশনের ক্ষেত্রে প্রযোজ্য। যেমন, নিম্নলিখিত কোডটি scaleX ১০ বার এবং scaleY ১৫ বার বাড়ায়:

Canvas(modifier = Modifier.fillMaxSize()) {
    scale(scaleX = 10f, scaleY = 15f) {
        drawCircle(Color.Blue, radius = 20.dp.toPx())
    }
}

অসমভাবে স্কেল করা একটি বৃত্ত
ছবি ৩. ক্যানভাসের কোনও বৃত্তে স্কেল অপারেশন প্রয়োগ করা।

অনুবাদ করুন

আপনার ড্রয়িং অপারেশন উপরে, নিচে, বাঁদিকে বা ডানদিকে সরাতে DrawScope.translate() ব্যবহার করুন। যেমন, নিম্নলিখিত কোডটি ড্রয়িংকে ১০০ পিক্সেল ডানদিকে এবং ৩০০ পিক্সেল উপরে সরায়:

Canvas(modifier = Modifier.fillMaxSize()) {
    translate(left = 100f, top = -300f) {
        drawCircle(Color.Blue, radius = 200.dp.toPx())
    }
}

কেন্দ্র থেকে সরে যাওয়া বৃত্ত
ছবি ৪. ক্যানভাসের কোনও বৃত্তে অনুবাদ অপারেশন প্রয়োগ করা হচ্ছে।

ঘোরান

পিভট পয়েন্টের চারপাশে আপনার ড্রয়িং অপারেশন রোটেট করতে DrawScope.rotate() ব্যবহার করুন। যেমন, নিম্নলিখিত কোডটি একটি আয়তক্ষেত্রকে ৪৫ ডিগ্রি কোণে ঘোরানোর কাজ করে:

Canvas(modifier = Modifier.fillMaxSize()) {
    rotate(degrees = 45F) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

স্ক্রিনের কেন্দ্রে ৪৫ ডিগ্রি ঘোরানো একটি আয়তক্ষেত্র সহ একটি ফোন
ছবি ৫. বর্তমান ড্রয়িং স্কোপে রোটেশন প্রয়োগ করতে আমরা rotate() ব্যবহার করি, এটি আয়তক্ষেত্রটিকে ৪৫ ডিগ্রি কোণে ঘুরিয়ে দেয়।

ইনসেট

বর্তমান DrawScope-এর ডিফল্ট প্যারামিটার অ্যাডজাস্ট করতে DrawScope.inset() ব্যবহার করুন, এর ফলে ড্রয়িংয়ের সীমানা পরিবর্তন করা এবং সেই অনুযায়ী ড্রয়িং ট্রান্সলেট করা হয়:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    inset(horizontal = 50f, vertical = 30f) {
        drawRect(color = Color.Green, size = canvasQuadrantSize)
    }
}

এই কোডটি কার্যকরভাবে ড্রয়িং কমান্ডে প্যাডিং যোগ করে:

চারপাশে প্যাডিং সহ একটি আয়তক্ষেত্র
ছবি ৬. ড্রয়িং কমান্ডে ইনসেট প্রয়োগ করা।

একাধিক ট্রান্সফর্মেশন

আপনার ড্রয়িংয়ে একাধিক ট্রান্সফর্মেশন প্রয়োগ করতে, DrawScope.withTransform() ফাংশন ব্যবহার করুন, যা আপনার পছন্দসই সব পরিবর্তনকে একত্রিত করে একটি ট্রান্সফর্মেশন তৈরি করে প্রয়োগ করে। আলাদা আলাদা ট্রান্সফর্মেশনের জন্য নেস্টেড কল করার চেয়ে withTransform() ব্যবহার করা বেশি কার্যকর, কারণ সমস্ত ট্রান্সফর্মেশন একসাথে একটি সিঙ্গেল অপারেশনে করা হয়। এর ফলে, Compose-কে প্রতিটি নেস্টেড ট্রান্সফর্মেশন গণনা ও সেভ করতে হয় না।

যেমন, নিম্নলিখিত কোডটি আয়তাকার ক্ষেত্রের উপর অনুবাদ ও ঘূর্ণন, দুটিই প্রয়োগ করে:

Canvas(modifier = Modifier.fillMaxSize()) {
    withTransform({
        translate(left = size.width / 5F)
        rotate(degrees = 45F)
    }) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

স্ক্রিনের পাশে সরিয়ে দেওয়া একটি রোটেট করা আয়তাকার আকৃতি সহ ফোন
ছবি ৭. একটি ঘূর্ণন ও একটি ট্রান্সলেশন, দুটিই প্রয়োগ করতে withTransform ব্যবহার করুন, এর ফলে আয়তক্ষেত্রটি ঘুরবে এবং বাঁদিকে সরে যাবে।

আঁকা সম্পর্কিত সাধারণ অপারেশন

টেক্সট আঁকা

Compose-এ টেক্সট ড্র করার জন্য, আপনি সাধারণত Text কম্পোজ করার উপযুক্ত আইটেম ব্যবহার করতে পারেন। তবে, আপনি যদি DrawScope-এ থাকেন অথবা কাস্টমাইজেশনের মাধ্যমে আপনার টেক্সট ম্যানুয়ালি ড্র করতে চান, তাহলে আপনি DrawScope.drawText() পদ্ধতি ব্যবহার করতে পারেন।

টেক্সট আঁকতে, rememberTextMeasurer ব্যবহার করে TextMeasurer তৈরি করুন এবং পরিমাপক সহ drawText-কে কল করুন:

val textMeasurer = rememberTextMeasurer()

Canvas(modifier = Modifier.fillMaxSize()) {
    drawText(textMeasurer, "Hello")
}

Canvas-এ আঁকা 'হ্যালো' দেখানো হচ্ছে
ছবি ৮. Canvas-এ টেক্সট আঁকা।

টেক্সট পরিমাপ করা

টেক্সট আঁকার প্রসেস অন্যান্য ড্রয়িং কমান্ডের থেকে কিছুটা আলাদা। সাধারণত, আপনি ড্রয়িং কমান্ডকে আকার (প্রস্থ ও উচ্চতা) দেন যাতে সেই অনুযায়ী শেপ/ছবি আঁকা যায়। টেক্সটের ক্ষেত্রে, রেন্ডার করা টেক্সটের সাইজ কন্ট্রোল করার জন্য কয়েকটি প্যারামিটার থাকে, যেমন ফন্ট সাইজ, ফন্ট, লিগেচার ও লেটার স্পেসিং।

Compose-এর মাধ্যমে, উপরে উল্লেখ করা ফ্যাক্টরগুলির উপর নির্ভর করে, টেক্সটের পরিমাপ করা সাইজ অ্যাক্সেস করতে, আপনি একটি TextMeasurer ব্যবহার করতে পারবেন। আপনি যদি টেক্সটের পিছনে একটি ব্যাকগ্রাউন্ড আঁকতে চান, তাহলে টেক্সটটি যে এলাকা জুড়ে আছে তার সাইজ পেতে পরিমাপ করা তথ্য ব্যবহার করতে পারেন:

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier = Modifier
        .drawWithCache {
            val measuredText =
                textMeasurer.measure(
                    AnnotatedString(longTextSample),
                    constraints = Constraints.fixedWidth((size.width * 2f / 3f).toInt()),
                    style = TextStyle(fontSize = 18.sp)
                )

            onDrawBehind {
                drawRect(pinkColor, size = measuredText.size.toSize())
                drawText(measuredText)
            }
        }
        .fillMaxSize()
)

এই কোড স্নিপেট টেক্সটের ব্যাকগ্রাউন্ড গোলাপি করে দেয়:

সম্পূর্ণ জায়গার ২/৩ অংশ জুড়ে একাধিক লাইনের টেক্সট, ব্যাকগ্রাউন্ডে আয়তাকার অংশ
ছবি ৯. ব্যাকগ্রাউন্ডে আয়তাকার বক্স সহ একাধিক লাইনের টেক্সট যা পুরো জায়গার ⅔ অংশ জুড়ে রয়েছে।

মাপ করা সাইজকে প্রভাবিত করে এমন সীমাবদ্ধতা, ফন্ট সাইজ বা কোনও প্রপার্টি অ্যাডজাস্ট করলে নতুন সাইজ রিপোর্ট করা হয়। আপনি width ও height দুটির জন্যই একটি নির্দিষ্ট সাইজ সেট করতে পারবেন এবং টেক্সটটি সেট করা TextOverflow মেনে চলবে। যেমন, নিম্নলিখিত কোডটি কম্পোজ করার উপযুক্ত জায়গার উচ্চতা ও প্রস্থের ⅓ অংশে টেক্সট রেন্ডার করে এবং TextOverflow-এর মান TextOverflow.Ellipsis হিসেবে সেট করে:

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier = Modifier
        .drawWithCache {
            val measuredText =
                textMeasurer.measure(
                    AnnotatedString(longTextSample),
                    constraints = Constraints.fixed(
                        width = (size.width / 3f).toInt(),
                        height = (size.height / 3f).toInt()
                    ),
                    overflow = TextOverflow.Ellipsis,
                    style = TextStyle(fontSize = 18.sp)
                )

            onDrawBehind {
                drawRect(pinkColor, size = measuredText.size.toSize())
                drawText(measuredText)
            }
        }
        .fillMaxSize()
)

টেক্সটটি এখন সীমাবদ্ধতার মধ্যে আঁকা হয়েছে এবং শেষে একটি উপবৃত্ত রয়েছে:

গোলাপি ব্যাকগ্রাউন্ডে টেক্সট লেখা হয়েছে, যেখানে উপবৃত্ত টেক্সটটি কেটে দিয়েছে।
ছবি ১০. TextOverflow.Ellipsis টেক্সট পরিমাপ করার ক্ষেত্রে নির্দিষ্ট সীমাবদ্ধতা আছে।

ছবি আঁকা

DrawScope ব্যবহার করে ImageBitmap আঁকতে, ImageBitmap.imageResource() ব্যবহার করে ছবি লোড করুন এবং তারপরে drawImage কল করুন:

val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)

Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
    drawImage(dogImage)
})

Canvas-এ আঁকা কুকুরের ছবি
ছবি ১১. Canvas-এ ImageBitmap আঁকছি।

প্রাথমিক আকার আঁকা

DrawScope-এ অনেক শেপ অঙ্কন ফাংশন আছে। কোনও শেপ আঁকতে, আগে থেকে নির্ধারিত ড্র ফাংশনের মধ্যে একটি ব্যবহার করুন, যেমন drawCircle:

val purpleColor = Color(0xFFBA68C8)
Canvas(
    modifier = Modifier
        .fillMaxSize()
        .padding(16.dp),
    onDraw = {
        drawCircle(purpleColor)
    }
)

API

আউটপুট

drawCircle()

বৃত্ত আঁকো

drawRect()

draw rect

drawRoundedRect()

বৃত্তাকার কোণবিশিষ্ট আয়তক্ষেত্র আঁকো

drawLine()

লাইন আঁকুন

drawOval()

ডিম্বাকার আঁকুন

drawArc()

আর্ক আঁকুন

drawPoints()

পয়েন্ট আঁকুন

পাথ আঁকুন

পাথ হল গাণিতিক নির্দেশাবলীর একটি সিরিজ যা এক্সিকিউট করা হলে একটি ড্রয়িং তৈরি হয়। DrawScope DrawScope.drawPath() পদ্ধতি ব্যবহার করে একটি পাথ আঁকতে পারে।

যেমন, আপনি একটি ত্রিভুজ আঁকতে চান। আপনি ড্রয়িং এরিয়ার সাইজ ব্যবহার করে lineTo() ও moveTo()-এর মতো ফাংশন সহ একটি পাথ তৈরি করতে পারবেন। তারপর, ত্রিভুজ পেতে এই নতুন তৈরি করা পাথ ব্যবহার করে drawPath()-কে কল করুন।

Spacer(
    modifier = Modifier
        .drawWithCache {
            val path = Path()
            path.moveTo(0f, 0f)
            path.lineTo(size.width / 2f, size.height / 2f)
            path.lineTo(size.width, 0f)
            path.close()
            onDrawBehind {
                drawPath(path, Color.Magenta, style = Stroke(width = 10f))
            }
        }
        .fillMaxSize()
)

Compose-এ আঁকা একটি উল্টো বেগুনি রঙের পাথ ট্রায়াঙ্গেল
ছবি ১২. Compose-এ Path তৈরি ও আঁকা।

Canvas অবজেক্ট অ্যাক্সেস করা

DrawScope-এর মাধ্যমে, আপনি Canvas অবজেক্টে সরাসরি অ্যাক্সেস পান না। আপনি Canvas অবজেক্টে অ্যাক্সেস পেতে DrawScope.drawIntoCanvas() ব্যবহার করতে পারেন যেখানে আপনি ফাংশন কল করতে পারবেন।

যেমন, আপনার কাছে কাস্টম Drawable থাকলে এবং আপনি সেটি ক্যানভাসের উপর আঁকতে চাইলে, আপনি ক্যানভাস অ্যাক্সেস করতে এবং Drawable#draw() কল করতে পারবেন, এর সাথে Canvas অবজেক্ট পাস করতে পারবেন:

val drawable = ShapeDrawable(OvalShape())
Spacer(
    modifier = Modifier
        .drawWithContent {
            drawIntoCanvas { canvas ->
                drawable.setBounds(0, 0, size.width.toInt(), size.height.toInt())
                drawable.draw(canvas.nativeCanvas)
            }
        }
        .fillMaxSize()
)

সম্পূর্ণ সাইজ জুড়ে থাকা ডিম্বাকার কালো ShapeDrawable
ছবি ১৩. Drawable আঁকতে ক্যানভাস অ্যাক্সেস করা।

আরও জানুন

Compose-এ ড্রয়িং সম্পর্কে আরও জানতে, নিম্নলিখিত রিসোর্স দেখুন: