কম্পোজে আকার

Compose-এর সাহায্যে আপনি বহুভুজ দিয়ে বিভিন্ন আকৃতি তৈরি করতে পারেন। উদাহরণস্বরূপ, আপনি নিম্নলিখিত ধরনের আকৃতিগুলো তৈরি করতে পারেন:

অঙ্কন এলাকার কেন্দ্রে নীল ষড়ভুজ
চিত্র ১। গ্রাফিক্স-শেপস লাইব্রেরি দিয়ে তৈরি করা যায় এমন বিভিন্ন আকৃতির উদাহরণ।

Compose-এ একটি কাস্টম গোলাকার বহুভুজ তৈরি করতে, আপনার app/build.gradlegraphics-shapes ডিপেন্ডেন্সিটি যোগ করুন:

implementation "androidx.graphics:graphics-shapes:1.0.1"

এই লাইব্রেরিটি আপনাকে বহুভুজ দিয়ে বিভিন্ন আকৃতি তৈরি করতে দেয়। যদিও বহুভুজ আকৃতির কেবল সরল প্রান্ত এবং ধারালো কোণা থাকে, এই আকৃতিগুলোতে ঐচ্ছিকভাবে গোলাকার কোণাও যোগ করা যায়। এটি দুটি ভিন্ন আকৃতির মধ্যে রূপান্তরকে সহজ করে তোলে। যেকোনো আকৃতির মধ্যে রূপান্তর করা কঠিন এবং এটি সাধারণত ডিজাইন-টাইমের একটি সমস্যা হয়ে থাকে। কিন্তু এই লাইব্রেরিটি একই ধরনের বহুভুজ কাঠামোযুক্ত আকৃতিগুলোর মধ্যে রূপান্তর ঘটিয়ে এই কাজটি সহজ করে দেয়।

বহুভুজ তৈরি করুন

নিচের কোডটি ড্রয়িং এলাকার কেন্দ্রে ৬টি পয়েন্ট ব্যবহার করে একটি সাধারণ বহুভুজ আকৃতি তৈরি করে:

Box(
    modifier = Modifier
        .drawWithCache {
            val roundedPolygon = RoundedPolygon(
                numVertices = 6,
                radius = size.minDimension / 2,
                centerX = size.width / 2,
                centerY = size.height / 2
            )
            val roundedPolygonPath = roundedPolygon.toPath().asComposePath()
            onDrawBehind {
                drawPath(roundedPolygonPath, color = Color.Blue)
            }
        }
        .fillMaxSize()
)

অঙ্কন এলাকার কেন্দ্রে নীল ষড়ভুজ
চিত্র ২। অঙ্কন ক্ষেত্রের কেন্দ্রে অবস্থিত নীল ষড়ভুজ।

এই উদাহরণে, লাইব্রেরিটি একটি RoundedPolygon তৈরি করে, যা অনুরোধ করা আকৃতিটির জ্যামিতি ধারণ করে। একটি Compose অ্যাপে সেই আকৃতিটি আঁকার জন্য, আপনাকে অবশ্যই এটি থেকে একটি Path অবজেক্ট নিতে হবে, যাতে আকৃতিটিকে এমন একটি ফর্মে আনা যায় যা Compose আঁকতে জানে।

বহুভুজের কোণগুলো গোলাকার করুন

একটি বহুভুজের কোণাগুলো গোলাকার করতে, CornerRounding প্যারামিটারটি ব্যবহার করুন। এটি radius এবং smoothing দুটি প্যারামিটার গ্রহণ করে। প্রতিটি গোলাকার কোণা ১-৩টি কিউবিক কার্ভ দিয়ে গঠিত হয়, যার কেন্দ্রটি একটি বৃত্তাকার চাপের আকৃতির হয় এবং দুটি পাশের ("ফ্ল্যাঙ্কিং") কার্ভ আকৃতিটির প্রান্ত থেকে কেন্দ্রের কার্ভ পর্যন্ত একটি রূপান্তর ঘটায়।

ব্যাসার্ধ

radius হলো শীর্ষবিন্দুকে বৃত্তাকারে আঁকতে ব্যবহৃত বৃত্তের ব্যাসার্ধ।

উদাহরণস্বরূপ, নিম্নলিখিত গোলাকার কোণার ত্রিভুজটি নিম্নরূপে তৈরি করা হয়:

গোলাকার কোণযুক্ত ত্রিভুজ
চিত্র ৩। গোলাকার কোণাবিশিষ্ট ত্রিভুজ।
গোলাকার ব্যাসার্ধ r গোলাকার কোণার বৃত্তাকার গোলাকার আকার নির্ধারণ করে।
চিত্র ৪। গোলাকারকরণ ব্যাসার্ধ r গোলাকার কোণাগুলোর বৃত্তাকার গোলাকারকরণের আকার নির্ধারণ করে।

মসৃণকরণ

স্মুদিং হলো এমন একটি ফ্যাক্টর যা নির্ধারণ করে কোণার বৃত্তাকার গোলাকার অংশ থেকে প্রান্ত পর্যন্ত পৌঁছাতে কত সময় লাগবে। স্মুদিং ফ্যাক্টর ০ (অমসৃণ, যা CornerRounding এর ডিফল্ট মান) হলে কোণাটি সম্পূর্ণরূপে বৃত্তাকারে গোলাকার হয়। একটি অশূন্য স্মুদিং ফ্যাক্টর (সর্বোচ্চ ১.০ পর্যন্ত) ব্যবহার করলে কোণাটি তিনটি পৃথক কার্ভের মাধ্যমে গোলাকার হয়।

০ (অমসৃণ) এর একটি মসৃণকরণ গুণক একটি একক ঘন বক্ররেখা তৈরি করে যা পূর্ববর্তী উদাহরণের মতো, নির্দিষ্ট বৃত্তাকার ব্যাসার্ধ সহ কোণার চারপাশে একটি বৃত্ত অনুসরণ করে।
চিত্র ৫। ০ স্মুথিং ফ্যাক্টর (অস্মুথড) একটি একক কিউবিক কার্ভ তৈরি করে যা পূর্ববর্তী উদাহরণের মতো নির্দিষ্ট রাউন্ডিং ব্যাসার্ধ সহ কোণার চারপাশে একটি বৃত্ত অনুসরণ করে।
একটি অশূন্য মসৃণকরণ গুণক শীর্ষবিন্দুকে গোলাকার করার জন্য তিনটি ঘন বক্ররেখা তৈরি করে: ভেতরের বৃত্তাকার বক্ররেখা (পূর্বের মতো) এবং দুটি পার্শ্ববর্তী বক্ররেখা যা ভেতরের বক্ররেখা ও বহুভুজের প্রান্তগুলোর মধ্যে সংযোগ স্থাপন করে।
চিত্র ৬। একটি অশূন্য মসৃণকরণ গুণক শীর্ষবিন্দুকে গোলাকার করার জন্য তিনটি ঘন বক্ররেখা তৈরি করে: ভেতরের বৃত্তাকার বক্ররেখা (পূর্বের মতোই) এবং দুটি পার্শ্ববর্তী বক্ররেখা যা ভেতরের বক্ররেখা ও বহুভুজের প্রান্তগুলোর মধ্যে সংযোগ স্থাপন করে।

উদাহরণস্বরূপ, নিচের কোড স্নিপেটটি স্মুদিং ০ এবং ১-এ সেট করার মধ্যেকার সূক্ষ্ম পার্থক্যটি তুলে ধরেছে:

Box(
    modifier = Modifier
        .drawWithCache {
            val roundedPolygon = RoundedPolygon(
                numVertices = 3,
                radius = size.minDimension / 2,
                centerX = size.width / 2,
                centerY = size.height / 2,
                rounding = CornerRounding(
                    size.minDimension / 10f,
                    smoothing = 0.1f
                )
            )
            val roundedPolygonPath = roundedPolygon.toPath().asComposePath()
            onDrawBehind {
                drawPath(roundedPolygonPath, color = Color.Black)
            }
        }
        .size(100.dp)
)

দুটি কালো ত্রিভুজ স্মুথিং প্যারামিটারের পার্থক্য দেখাচ্ছে।
চিত্র ৭। দুটি কালো ত্রিভুজ যা স্মুথিং প্যারামিটারের পার্থক্য দেখাচ্ছে।

আকার এবং অবস্থান

ডিফল্টরূপে, কেন্দ্র ( 0, 0 ) এর চারপাশে 1 ব্যাসার্ধ নিয়ে একটি আকৃতি তৈরি করা হয়। এই ব্যাসার্ধটি কেন্দ্র এবং যে বহুভুজের উপর আকৃতিটি ভিত্তি করে তৈরি তার বাইরের শীর্ষবিন্দুগুলির মধ্যে দূরত্বকে বোঝায়। মনে রাখবেন যে কোণগুলি গোলাকার করলে আকৃতিটি ছোট হয়ে যায়, কারণ গোলাকার করা শীর্ষবিন্দুগুলির চেয়ে গোলাকার কোণগুলি কেন্দ্রের বেশি কাছাকাছি থাকে। একটি বহুভুজের আকার পরিবর্তন করতে, radius মান সামঞ্জস্য করুন। অবস্থান সামঞ্জস্য করতে, বহুভুজটির centerX বা centerY পরিবর্তন করুন। বিকল্পভাবে, DrawScope#translate() এর মতো স্ট্যান্ডার্ড DrawScope ট্রান্সফরমেশন ফাংশন ব্যবহার করে অবজেক্টটিকে ট্রান্সফর্ম করে এর আকার, অবস্থান এবং ঘূর্ণন পরিবর্তন করুন।

আকার পরিবর্তন করুন

একটি Morph অবজেক্ট হলো একটি নতুন আকৃতি যা দুটি বহুভুজ আকৃতির মধ্যে একটি অ্যানিমেশন উপস্থাপন করে। দুটি আকৃতির মধ্যে মর্ফ করতে, দুটি RoundedPolygons এবং একটি Morph অবজেক্ট তৈরি করুন যা এই দুটি আকৃতি গ্রহণ করে। শুরু এবং শেষের আকৃতির মধ্যে একটি আকৃতি গণনা করতে, শুরুর (0) এবং শেষের (1) আকৃতির মধ্যে এর রূপ নির্ধারণ করার জন্য শূন্য এবং একের মধ্যে একটি progress মান প্রদান করুন:

Box(
    modifier = Modifier
        .drawWithCache {
            val triangle = RoundedPolygon(
                numVertices = 3,
                radius = size.minDimension / 2f,
                centerX = size.width / 2f,
                centerY = size.height / 2f,
                rounding = CornerRounding(
                    size.minDimension / 10f,
                    smoothing = 0.1f
                )
            )
            val square = RoundedPolygon(
                numVertices = 4,
                radius = size.minDimension / 2f,
                centerX = size.width / 2f,
                centerY = size.height / 2f
            )

            val morph = Morph(start = triangle, end = square)
            val morphPath = morph
                .toPath(progress = 0.5f).asComposePath()

            onDrawBehind {
                drawPath(morphPath, color = Color.Black)
            }
        }
        .fillMaxSize()
)

উপরের উদাহরণে, অগ্রগতিটি দুটি আকৃতির (গোলাকার ত্রিভুজ এবং একটি বর্গক্ষেত্র) ঠিক মাঝখানে রয়েছে, যার ফলে নিম্নলিখিত ফলাফলটি পাওয়া যায়:

একটি গোলাকার ত্রিভুজ এবং একটি বর্গক্ষেত্রের মধ্যবর্তী পথের ৫০%।
চিত্র ৮। একটি গোলাকার ত্রিভুজ এবং একটি বর্গক্ষেত্রের মধ্যবর্তী পথের ৫০%।

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

val infiniteAnimation = rememberInfiniteTransition(label = "infinite animation")
val morphProgress = infiniteAnimation.animateFloat(
    initialValue = 0f,
    targetValue = 1f,
    animationSpec = infiniteRepeatable(
        tween(500),
        repeatMode = RepeatMode.Reverse
    ),
    label = "morph"
)
Box(
    modifier = Modifier
        .drawWithCache {
            val triangle = RoundedPolygon(
                numVertices = 3,
                radius = size.minDimension / 2f,
                centerX = size.width / 2f,
                centerY = size.height / 2f,
                rounding = CornerRounding(
                    size.minDimension / 10f,
                    smoothing = 0.1f
                )
            )
            val square = RoundedPolygon(
                numVertices = 4,
                radius = size.minDimension / 2f,
                centerX = size.width / 2f,
                centerY = size.height / 2f
            )

            val morph = Morph(start = triangle, end = square)
            val morphPath = morph
                .toPath(progress = morphProgress.value)
                .asComposePath()

            onDrawBehind {
                drawPath(morphPath, color = Color.Black)
            }
        }
        .fillMaxSize()
)

বর্গক্ষেত্র এবং গোলাকার ত্রিভুজের মধ্যে অবিরাম রূপান্তর।
চিত্র ৯। একটি বর্গক্ষেত্র এবং একটি গোলাকার ত্রিভুজের মধ্যে অসীমভাবে রূপান্তর।

ক্লিপ হিসাবে বহুভুজ ব্যবহার করুন

কোনো কম্পোজেবল কন্টেন্টের রেন্ডারিং পদ্ধতি পরিবর্তন করতে, এবং ক্লিপিং এরিয়ার চারপাশে তৈরি হওয়া শ্যাডোর সুবিধা নিতে কম্পোজে clip মডিফায়ার ব্যবহার করা একটি প্রচলিত পদ্ধতি।

fun RoundedPolygon.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) }
class RoundedPolygonShape(
    private val polygon: RoundedPolygon,
    private var matrix: Matrix = Matrix()
) : Shape {
    private var path = Path()
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        path.rewind()
        path = polygon.toPath().asComposePath()
        matrix.reset()
        val bounds = polygon.getBounds()
        val maxDimension = max(bounds.width, bounds.height)
        matrix.scale(size.width / maxDimension, size.height / maxDimension)
        matrix.translate(-bounds.left, -bounds.top)

        path.transform(matrix)
        return Outline.Generic(path)
    }
}

এরপর আপনি পলিগনটিকে একটি ক্লিপ হিসেবে ব্যবহার করতে পারেন, যেমনটি নিচের কোড স্নিপেটে দেখানো হয়েছে:

val hexagon = remember {
    RoundedPolygon(
        6,
        rounding = CornerRounding(0.2f)
    )
}
val clip = remember(hexagon) {
    RoundedPolygonShape(polygon = hexagon)
}
Box(
    modifier = Modifier
        .clip(clip)
        .background(MaterialTheme.colorScheme.secondary)
        .size(200.dp)
) {
    Text(
        "Hello Compose",
        color = MaterialTheme.colorScheme.onSecondary,
        modifier = Modifier.align(Alignment.Center)
    )
}

এর ফলে নিম্নলিখিত বিষয়গুলো ঘটে:

কেন্দ্রে `hello compose` লেখা সহ একটি ষড়ভুজ।
চিত্র ১০। কেন্দ্রে "Hello Compose" লেখা সহ একটি ষড়ভুজ।

এটি আগে যা রেন্ডার হচ্ছিল তার থেকে খুব একটা আলাদা নাও লাগতে পারে, কিন্তু এটি কম্পোজের অন্যান্য বৈশিষ্ট্য কাজে লাগানোর সুযোগ করে দেয়। উদাহরণস্বরূপ, এই কৌশলটি ব্যবহার করে একটি ছবি ক্লিপ করা যায় এবং ক্লিপ করা অঞ্চলের চারপাশে একটি ছায়া প্রয়োগ করা যায়:

val hexagon = remember {
    RoundedPolygon(
        6,
        rounding = CornerRounding(0.2f)
    )
}
val clip = remember(hexagon) {
    RoundedPolygonShape(polygon = hexagon)
}
Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center
) {
    Image(
        painter = painterResource(id = R.drawable.dog),
        contentDescription = "Dog",
        contentScale = ContentScale.Crop,
        modifier = Modifier
            .graphicsLayer {
                this.shadowElevation = 6.dp.toPx()
                this.shape = clip
                this.clip = true
                this.ambientShadowColor = Color.Black
                this.spotShadowColor = Color.Black
            }
            .size(200.dp)

    )
}

ষড়ভুজের মধ্যে কুকুর, যার কিনারা বরাবর ছায়া প্রয়োগ করা হয়েছে।
চিত্র ১১। ক্লিপ হিসাবে প্রয়োগ করা কাস্টম আকৃতি।

ক্লিক করলে মরফ বাটন

আপনি graphics-shape লাইব্রেরি ব্যবহার করে এমন একটি বাটন তৈরি করতে পারেন যা চাপ দিলে দুটি আকারের মধ্যে রূপান্তরিত হয়। প্রথমে, Shape এক্সটেন্ড করে একটি MorphPolygonShape তৈরি করুন এবং এটিকে যথাযথভাবে ফিট করার জন্য স্কেল ও ট্রান্সলেট করুন। আকারটিকে অ্যানিমেট করার জন্য এতে progress পাস করার বিষয়টি লক্ষ্য করুন:

class MorphPolygonShape(
    private val morph: Morph,
    private val percentage: Float
) : Shape {

    private val matrix = Matrix()
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f
        // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y.
        matrix.scale(size.width / 2f, size.height / 2f)
        matrix.translate(1f, 1f)

        val path = morph.toPath(progress = percentage).asComposePath()
        path.transform(matrix)
        return Outline.Generic(path)
    }
}

এই মর্ফ শেপটি ব্যবহার করতে, shapeA এবং shapeB দুটি পলিগন তৈরি করুন। Morph তৈরি করুন এবং মনে রাখুন। তারপর, অ্যানিমেশনের চালিকা শক্তি হিসেবে interactionSource on press` ব্যবহার করে, মর্ফটিকে একটি ক্লিপ আউটলাইন হিসেবে বাটনে প্রয়োগ করুন।

val shapeA = remember {
    RoundedPolygon(
        6,
        rounding = CornerRounding(0.2f)
    )
}
val shapeB = remember {
    RoundedPolygon.star(
        6,
        rounding = CornerRounding(0.1f)
    )
}
val morph = remember {
    Morph(shapeA, shapeB)
}
val interactionSource = remember {
    MutableInteractionSource()
}
val isPressed by interactionSource.collectIsPressedAsState()
val animatedProgress = animateFloatAsState(
    targetValue = if (isPressed) 1f else 0f,
    label = "progress",
    animationSpec = spring(dampingRatio = 0.4f, stiffness = Spring.StiffnessMedium)
)
Box(
    modifier = Modifier
        .size(200.dp)
        .padding(8.dp)
        .clip(MorphPolygonShape(morph, animatedProgress.value))
        .background(Color(0xFF80DEEA))
        .size(200.dp)
        .clickable(interactionSource = interactionSource, indication = null) {
        }
) {
    Text("Hello", modifier = Modifier.align(Alignment.Center))
}

এর ফলে বক্সটিতে ট্যাপ করলে নিম্নলিখিত অ্যানিমেশনটি দেখা যায়:

দুটি আকারের মধ্যে ক্লিক হিসাবে রূপান্তর প্রয়োগ করা হয়।
চিত্র ১২। দুটি আকৃতির মধ্যে ক্লিকের মাধ্যমে প্রয়োগ করা রূপান্তর।

অসীমভাবে আকৃতি পরিবর্তনের অ্যানিমেশন

একটি মর্ফ শেপকে অবিরাম অ্যানিমেট করতে, rememberInfiniteTransition ব্যবহার করুন। নিচে একটি প্রোফাইল ছবির উদাহরণ দেওয়া হলো যা সময়ের সাথে সাথে অসীমভাবে আকৃতি পরিবর্তন করে (এবং ঘোরে)। এই পদ্ধতিতে উপরে দেখানো MorphPolygonShape এ একটি ছোট পরিবর্তন করা হয়:

class CustomRotatingMorphShape(
    private val morph: Morph,
    private val percentage: Float,
    private val rotation: Float
) : Shape {

    private val matrix = Matrix()
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        // Below assumes that you haven't changed the default radius of 1f, nor the centerX and centerY of 0f
        // By default this stretches the path to the size of the container, if you don't want stretching, use the same size.width for both x and y.
        matrix.scale(size.width / 2f, size.height / 2f)
        matrix.translate(1f, 1f)
        matrix.rotateZ(rotation)

        val path = morph.toPath(progress = percentage).asComposePath()
        path.transform(matrix)

        return Outline.Generic(path)
    }
}

@Preview
@Composable
private fun RotatingScallopedProfilePic() {
    val shapeA = remember {
        RoundedPolygon(
            12,
            rounding = CornerRounding(0.2f)
        )
    }
    val shapeB = remember {
        RoundedPolygon.star(
            12,
            rounding = CornerRounding(0.2f)
        )
    }
    val morph = remember {
        Morph(shapeA, shapeB)
    }
    val infiniteTransition = rememberInfiniteTransition("infinite outline movement")
    val animatedProgress = infiniteTransition.animateFloat(
        initialValue = 0f,
        targetValue = 1f,
        animationSpec = infiniteRepeatable(
            tween(2000, easing = LinearEasing),
            repeatMode = RepeatMode.Reverse
        ),
        label = "animatedMorphProgress"
    )
    val animatedRotation = infiniteTransition.animateFloat(
        initialValue = 0f,
        targetValue = 360f,
        animationSpec = infiniteRepeatable(
            tween(6000, easing = LinearEasing),
            repeatMode = RepeatMode.Reverse
        ),
        label = "animatedMorphProgress"
    )
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Image(
            painter = painterResource(id = R.drawable.dog),
            contentDescription = "Dog",
            contentScale = ContentScale.Crop,
            modifier = Modifier
                .clip(
                    CustomRotatingMorphShape(
                        morph,
                        animatedProgress.value,
                        animatedRotation.value
                    )
                )
                .size(200.dp)
        )
    }
}

এই কোডটি নিম্নলিখিত মজার ফলাফল দেয়:

হৃদয় আকৃতি
চিত্র ১৩। একটি ঘূর্ণায়মান ঢেউখেলানো আকৃতি দ্বারা খণ্ডিত প্রোফাইল ছবি।

কাস্টম বহুভুজ

যদি সুষম বহুভুজ থেকে তৈরি আকৃতি আপনার প্রয়োজন মেটাতে না পারে, তবে আপনি শীর্ষবিন্দুর একটি তালিকা ব্যবহার করে আরও নিজস্ব আকৃতির সৃষ্টি করতে পারেন। উদাহরণস্বরূপ, আপনি হয়তো এইরকম একটি হৃদয়ের আকৃতি তৈরি করতে চাইতে পারেন:

হৃদয় আকৃতি
চিত্র ১৪। হৃদয় আকৃতি।

আপনি RoundedPolygon ওভারলোডটি ব্যবহার করে এই আকৃতিটির প্রতিটি শীর্ষবিন্দু নির্দিষ্ট করতে পারেন, যা x, y স্থানাঙ্কের একটি ফ্লোট অ্যারে গ্রহণ করে।

হৃদয় বহুভুজটিকে বিশ্লেষণ করতে গেলে লক্ষ্য করুন যে, বিন্দু নির্দিষ্ট করার জন্য পোলার স্থানাঙ্ক ব্যবস্থা ব্যবহার করা কার্টেসিয়ান (x,y) স্থানাঙ্ক ব্যবস্থা ব্যবহারের চেয়ে সহজ, যেখানে ডান দিক থেকে শুরু হয়ে ঘড়ির কাঁটার দিকে অগ্রসর হয় এবং 270° থাকে ১২টার অবস্থানে:

হৃদয় আকৃতি
চিত্র ১৫। স্থানাঙ্কসহ হৃদয়ের আকৃতি।

এখন প্রতিটি বিন্দুতে কেন্দ্র থেকে কোণ (𝜭) এবং ব্যাসার্ধ উল্লেখ করে আকৃতিটি আরও সহজে সংজ্ঞায়িত করা যেতে পারে:

হৃদয় আকৃতি
চিত্র ১৬। স্থানাঙ্কসহ হৃদয়ের আকৃতি, কোনো গোলাকরণ ছাড়া।

এখন শীর্ষবিন্দুগুলো তৈরি করে RoundedPolygon ফাংশনে পাঠানো যাবে:

val vertices = remember {
    val radius = 1f
    val radiusSides = 0.8f
    val innerRadius = .1f
    floatArrayOf(
        radialToCartesian(radiusSides, 0f.toRadians()).x,
        radialToCartesian(radiusSides, 0f.toRadians()).y,
        radialToCartesian(radius, 90f.toRadians()).x,
        radialToCartesian(radius, 90f.toRadians()).y,
        radialToCartesian(radiusSides, 180f.toRadians()).x,
        radialToCartesian(radiusSides, 180f.toRadians()).y,
        radialToCartesian(radius, 250f.toRadians()).x,
        radialToCartesian(radius, 250f.toRadians()).y,
        radialToCartesian(innerRadius, 270f.toRadians()).x,
        radialToCartesian(innerRadius, 270f.toRadians()).y,
        radialToCartesian(radius, 290f.toRadians()).x,
        radialToCartesian(radius, 290f.toRadians()).y,
    )
}

এই radialToCartesian ফাংশনটি ব্যবহার করে শীর্ষবিন্দুগুলোকে কার্টেসিয়ান স্থানাঙ্কে রূপান্তর করতে হবে:

internal fun Float.toRadians() = this * PI.toFloat() / 180f

internal val PointZero = PointF(0f, 0f)
internal fun radialToCartesian(
    radius: Float,
    angleRadians: Float,
    center: PointF = PointZero
) = directionVectorPointF(angleRadians) * radius + center

internal fun directionVectorPointF(angleRadians: Float) =
    PointF(cos(angleRadians), sin(angleRadians))

পূর্ববর্তী কোডটি আপনাকে হার্টটির মূল ভার্টেক্সগুলো দেয়, কিন্তু নির্বাচিত হার্টের আকৃতিটি পেতে আপনাকে নির্দিষ্ট কিছু কোণা গোলাকার করতে হবে। 90° এবং 270° কোণাগুলোতে কোনো গোলাকারকরণ নেই, কিন্তু অন্য কোণাগুলোতে আছে। প্রতিটি কোণার জন্য নিজস্ব গোলাকারকরণ করতে, perVertexRounding প্যারামিটারটি ব্যবহার করুন:

val rounding = remember {
    val roundingNormal = 0.6f
    val roundingNone = 0f
    listOf(
        CornerRounding(roundingNormal),
        CornerRounding(roundingNone),
        CornerRounding(roundingNormal),
        CornerRounding(roundingNormal),
        CornerRounding(roundingNone),
        CornerRounding(roundingNormal),
    )
}

val polygon = remember(vertices, rounding) {
    RoundedPolygon(
        vertices = vertices,
        perVertexRounding = rounding
    )
}
Box(
    modifier = Modifier
        .drawWithCache {
            val roundedPolygonPath = polygon.toPath().asComposePath()
            onDrawBehind {
                scale(size.width * 0.5f, size.width * 0.5f) {
                    translate(size.width * 0.5f, size.height * 0.5f) {
                        drawPath(roundedPolygonPath, color = Color(0xFFF15087))
                    }
                }
            }
        }
        .size(400.dp)
)

এর ফলে গোলাপী হৃদয়টি তৈরি হয়:

হৃদয় আকৃতি
চিত্র ১৭। হৃৎপিণ্ড আকৃতির ফলাফল।

যদি পূর্ববর্তী আকারগুলি আপনার ব্যবহারের প্রয়োজন মেটাতে না পারে, তবে একটি কাস্টম আকার আঁকতে Path ক্লাস ব্যবহার করার কথা ভাবতে পারেন, অথবা ডিস্ক থেকে একটি ImageVector ফাইল লোড করতে পারেন। graphics-shapes লাইব্রেরিটি যথেচ্ছ আকারের জন্য ব্যবহারের উদ্দেশ্যে তৈরি নয়, বরং এটি বিশেষভাবে গোলাকার বহুভুজ তৈরি এবং তাদের মধ্যে মর্ফ অ্যানিমেশন সহজ করার জন্য তৈরি করা হয়েছে।

অতিরিক্ত সম্পদ

আরও তথ্য ও উদাহরণের জন্য, নিম্নলিখিত উৎসগুলো দেখুন: