รูปร่างใน Compose

ซึ่งจะช่วยให้คุณสร้างรูปร่างที่เกิดจากรูปหลายเหลี่ยมได้ ตัวอย่างเช่น คุณสามารถสร้างรูปร่างต่อไปนี้ได้

วันที่ รูปหกเหลี่ยมสีน้ำเงินใจกลางพื้นที่วาดภาพ
รูปที่ 1 ตัวอย่างรูปร่างต่างๆ ที่คุณสามารถทำได้ด้วยรูปร่างกราฟิก คลัง

หากต้องการสร้างรูปหลายเหลี่ยมทรงกลมที่กำหนดเองใน Compose ให้เพิ่มแอตทริบิวต์ ทรัพยากร Dependency graphics-shapes ต่อ app/build.gradle:

implementation "androidx.graphics:graphics-shapes:1.0.0-rc01"

ไลบรารีนี้ช่วยให้คุณสร้างรูปร่างที่เกิดจากรูปหลายเหลี่ยมได้ ขณะที่รูปหลายเหลี่ยม รูปร่างต่างๆ จะมีเฉพาะขอบตรงและมุมแหลม ทำให้สามารถ มุมโค้งมน (ไม่บังคับ) ทำให้สามารถปรับเปลี่ยนระหว่าง รูปร่าง การแปลงสับเปลี่ยนนั้นยากระหว่างรูปทรงที่กำหนดเอง และมีแนวโน้มที่จะ เวลาออกแบบ แต่ไลบรารีนี้ก็ช่วยให้เข้าใจได้ง่ายขึ้นด้วยการ ที่มีโครงสร้างรูปหลายเหลี่ยม คล้ายๆ กัน

สร้างรูปหลายเหลี่ยม

ข้อมูลโค้ดต่อไปนี้สร้างรูปร่างรูปหลายเหลี่ยมพื้นฐานที่มี 6 จุดตรงกลาง ของพื้นที่ภาพวาด:

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

วันที่ รูปหกเหลี่ยมสีน้ำเงินใจกลางพื้นที่วาดภาพ
รูปที่ 2 รูปหกเหลี่ยมสีน้ำเงินใจกลางพื้นที่วาดภาพ

ในตัวอย่างนี้ ไลบรารีสร้าง RoundedPolygon ที่เก็บเรขาคณิต แทนรูปร่างที่ขอ หากต้องการวาดรูปร่างนั้นในแอปเขียน คุณต้องรับออบเจ็กต์ Path จากออบเจ็กต์ดังกล่าวจึงจะได้รูปร่างในแบบฟอร์มที่ Compose รู้วิธีวาด

มนมุมของรูปหลายเหลี่ยม

หากต้องการปัดเศษมุมของรูปหลายเหลี่ยม ให้ใช้พารามิเตอร์ CornerRounding ช่วงเวลานี้ ต้องมีพารามิเตอร์ 2 รายการ คือ radius และ smoothing มุมโค้งมนแต่ละมุมประกอบขึ้น เส้นโค้ง 1-3 ลูกบาศก์ โดยตรงกลางจะมีรูปทรงโค้งเป็นวงกลมในขณะที่เส้นทั้งสอง เส้นโค้งด้านข้าง ("flanking") จะเปลี่ยนจากขอบของรูปร่างไปยังเส้นโค้งตรงกลาง

รัศมี

radius คือรัศมีของวงกลมที่ใช้ปัดเศษจุดยอดมุม

ตัวอย่าง สามเหลี่ยมมุมมนต่อไปนี้มีรูปร่างดังนี้

วันที่ รูปสามเหลี่ยมมุมโค้ง
รูปที่ 3 รูปสามเหลี่ยมมุมโค้งมน
รัศมีการปัดเศษ r จะกำหนดขนาดการปัดเศษวงกลมของ
มุมโค้งมน
รูปที่ 4 รัศมีแบบวงกลม r กำหนดขนาดการปัดเศษของรูปวงกลมของ มุมโค้งมน

การเกลี่ย

ความราบรื่นเป็นปัจจัยที่กำหนดระยะเวลาที่ใช้ในการเริ่มต้น มุมกลมมนเข้ากับขอบ ค่าการปรับให้เรียบเนียนเป็น 0 (ไม่ราบรื่น ค่าเริ่มต้นสำหรับ CornerRounding) จะแสดงเป็นวงกลมเท่านั้น มุมโค้งมน ปัจจัยที่ทำให้ผิวเรียบเนียนแบบไม่เป็นศูนย์ (ไม่เกิน 1.0) จะส่งผลให้เกิด มุมที่มีการปัดเศษโดยเส้นโค้ง 3 เส้น

วันที่ ตัวประกอบการปรับให้เรียบของ 0 (ไม่เรียบ) จะสร้างเส้นโค้งรูปลูกบาศก์เดี่ยวซึ่ง
ติดตามวงกลมมุมที่มีรัศมีโค้งมนตามที่ระบุ ดังเช่นใน
ตัวอย่างก่อนหน้านี้
รูปที่ 5 ตัวประกอบการปรับให้เรียบของ 0 (ไม่เรียบ) ทำให้เกิดเส้นโค้งรูปลูกบาศก์เดี่ยว ตามหลังวงกลมมุมที่มีรัศมีโค้งมนตามที่ระบุ เช่น ในตัวอย่างก่อนหน้านี้
ตัวประกอบที่ทำให้เรียบไม่ใช่ 0 จะสร้างเส้นโค้ง 3 ลูกบาศก์เพื่อปัดเศษ
จุดยอดมุม: เส้นโค้งวงกลมด้านใน (เช่นก่อนหน้านี้) บวกกับเส้นโค้งขนาบข้าง 2 เส้นที่
การเปลี่ยนระหว่างเส้นโค้งด้านในและขอบรูปหลายเหลี่ยม
รูปที่ 6 ตัวประกอบที่ทำให้เรียบไม่ใช่ 0 จะสร้างเส้นโค้ง 3 ลูกบาศก์เพื่อปัดเศษ จุดยอดมุม: เส้นโค้งวงกลมด้านใน (เช่นก่อนหน้านี้) บวกกับเส้นโค้งขนาบข้าง 2 เส้นที่ การเปลี่ยนผ่านระหว่างเส้นโค้งด้านในกับขอบรูปหลายเหลี่ยม

ตัวอย่างเช่น ข้อมูลโค้ดด้านล่างแสดงความแตกต่างเล็กน้อยในการตั้งค่า ปรับเรียบเป็น 0 ต่อ 1

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

วันที่ สามเหลี่ยมสีดำ 2 อันแสดงความแตกต่างของการปรับให้เรียบ
พารามิเตอร์
รูปที่ 7 สามเหลี่ยมสีดำ 2 รูปแสดงความแตกต่างของพารามิเตอร์การปรับให้เรียบ

ขนาดและตำแหน่ง

โดยค่าเริ่มต้น รูปร่างจะสร้างขึ้นโดยมีรัศมี 1 รอบศูนย์กลาง (0, 0) รัศมีนี้แสดงระยะห่างระหว่างจุดศูนย์กลางกับจุดยอดภายนอก รูปหลายเหลี่ยมที่ใช้รูปร่าง โปรดทราบว่าการปัดเศษมุม ทำให้มีรูปร่างเล็กลงเนื่องจากมุมโค้งมนจะอยู่ใกล้กับ จุดกึ่งกลางมากกว่าจุดยอดที่ปัดเศษ หากต้องการปรับขนาดรูปหลายเหลี่ยม ให้ปรับradius หากต้องการปรับตำแหน่ง ให้เปลี่ยน centerX หรือ centerY ของรูปหลายเหลี่ยม หรือเปลี่ยนรูปแบบวัตถุเพื่อเปลี่ยนขนาด ตำแหน่ง และการหมุน ใช้ฟังก์ชันการแปลง DrawScope มาตรฐาน เช่น DrawScope#translate()

รูปร่างที่เกิดจากการดัดแปลง

วัตถุ Morph เป็นรูปร่างใหม่ที่แสดงถึงภาพเคลื่อนไหวระหว่างรูปหลายเหลี่ยม 2 รูป รูปร่าง หากต้องการเปลี่ยนระหว่างรูปร่าง 2 แบบ ให้สร้าง RoundedPolygons 2 รายการและ Morph ที่มีรูปร่าง 2 แบบนี้ เพื่อคำนวณรูปร่างระหว่างจุดเริ่มต้นและ รูปทรงสิ้นสุด ให้ระบุค่า progress ระหว่าง 0 ถึง 1 เพื่อกำหนด ระหว่างรูปร่างเริ่มต้น (0) ถึงรูปร่างสิ้นสุด (1) ดังนี้

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

ในตัวอย่างด้านบน ความคืบหน้าคือกึ่งกลางระหว่าง 2 รูปร่างพอดี (สามเหลี่ยมกลมและสี่เหลี่ยมจัตุรัส) ให้ผลลัพธ์ดังต่อไปนี้

วันที่ 50% ของความยาวระหว่างรูปสามเหลี่ยมมุมมนกับสี่เหลี่ยมจัตุรัส
รูปที่ 8 50% ของความยาวระหว่างรูปสามเหลี่ยมมุมมนกับสี่เหลี่ยมจัตุรัส

ในสถานการณ์ส่วนใหญ่ การมอร์ฟิกจะทำโดยเป็นส่วนหนึ่งของภาพเคลื่อนไหว ไม่ใช่แค่ การแสดงภาพแบบคงที่ หากต้องการให้มีการเคลื่อนไหวระหว่าง 2 แท็กนี้ คุณสามารถใช้มาตรฐาน API ภาพเคลื่อนไหวใน Compose เพื่อเปลี่ยนแปลง ค่าความคืบหน้าเมื่อเวลาผ่านไป ตัวอย่างเช่น คุณสามารถทำให้มอร์ฟเคลื่อนไหวได้ไม่รู้จบ ระหว่างรูปร่าง 2 รูปนี้ ดังนี้

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

วันที่ การสับเปลี่ยนระหว่างรูปสี่เหลี่ยมจัตุรัสและสามเหลี่ยมมุมมน
รูปที่ 9 การสับเปลี่ยนระหว่างรูปสี่เหลี่ยมจัตุรัสกับสามเหลี่ยมมุมมนอย่างไม่มีที่สิ้นสุด

ใช้รูปหลายเหลี่ยมเป็นคลิป

เป็นเรื่องปกติที่จะใช้ clip ตัวแก้ไขใน Compose เพื่อเปลี่ยนแปลงวิธีแสดงผล Composable และเมื่อต้องการ ประโยชน์ของเงาที่วาดรอบๆ พื้นที่การตัด

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

ซึ่งจะส่งผลดังต่อไปนี้

วันที่ รูปหกเหลี่ยมพร้อมข้อความ "Hey Compose" ตรงกลาง
รูปที่ 10 รูปหกเหลี่ยมที่มีข้อความ "สวัสดีเขียน" ให้อยู่กึ่งกลาง

ซึ่งอาจดูไม่แตกต่างจากการแสดงภาพก่อนหน้านี้ แต่ก็ช่วยให้ ในการใช้ประโยชน์จากฟีเจอร์อื่นๆ ใน 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)

    )
}

วันที่ สุนัขในรูปหกเหลี่ยมที่มีเงาติดรอบขอบ
รูปที่ 11 ใช้รูปร่างที่กำหนดเองเป็นคลิป

ปุ่ม Morph เมื่อคลิก

คุณใช้ไลบรารี graphics-shape เพื่อสร้างปุ่มที่สับเปลี่ยนระหว่าง 2 รูปร่างเมื่อกด ก่อนอื่น ให้สร้าง MorphPolygonShape ที่ขยาย Shape การปรับขนาดเพื่อปรับขนาดให้เหมาะสม สังเกตการผ่านใน ความคืบหน้าเพื่อให้รูปร่างเคลื่อนไหวได้ ดังนี้

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

หากต้องการใช้รูปร่างที่มีการมอร์ฟนี้ ให้สร้างรูปหลายเหลี่ยม 2 รูป คือ shapeA และ shapeB สร้างและ จดจำ Morph จากนั้นใช้มอร์ฟกับปุ่มในรูปแบบของคลิป โดยใช้ interactionSource ในสื่อเป็นแรงขับเคลื่อนเบื้องหลัง ภาพเคลื่อนไหว:

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

ซึ่งจะทำให้มีภาพเคลื่อนไหวต่อไปนี้เมื่อแตะกล่อง

วันที่ การใช้ Morph เป็นคลิกระหว่างรูปร่าง 2 แบบ
รูปที่ 12 การใช้ Morph เป็นการคลิกระหว่าง 2 รูปร่าง

สร้างภาพเคลื่อนไหวที่สับเปลี่ยนไปเรื่อยๆ

หากต้องการสร้างการเคลื่อนไหวของรูปร่างที่สับเปลี่ยนไปเรื่อยๆ ให้ใช้ 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)
        )
    }
}

โค้ดนี้จะให้ผลลัพธ์ที่น่าสนใจดังนี้

วันที่ รูปหัวใจ
รูปที่ 13 รูปโปรไฟล์ที่ตัดตามรูปร่างที่หมุนเป็นเกลียว

รูปหลายเหลี่ยมที่กำหนดเอง

หากรูปทรงที่สร้างจากรูปหลายเหลี่ยมปกติไม่ครอบคลุมกรณีการใช้งาน คุณสามารถ สร้างรูปร่างที่กำหนดเองเพิ่มเติมด้วยรายการจุดยอด ตัวอย่างเช่น คุณอาจต้องการ สร้างเป็นรูปหัวใจดังนี้

วันที่ รูปหัวใจ
รูปที่ 14 รูปหัวใจ

คุณสามารถระบุจุดยอดแต่ละจุดของรูปร่างนี้ได้โดยใช้ RoundedPolygon โอเวอร์โหลดที่ใช้อาร์เรย์ลอยตัวของพิกัด x, y

เมื่อต้องการแยกรูปหลายเหลี่ยมรูปหัวใจ ให้สังเกตว่าระบบพิกัดเชิงขั้วสำหรับ การระบุจุดทำให้ง่ายกว่าการใช้พิกัดรถเข็น (x,y) โดย จะเริ่มต้นที่ด้านขวามือ และดำเนินการตามเข็มนาฬิกาด้วย 270° ที่ตำแหน่ง 12 นาฬิกา:

วันที่ รูปหัวใจ
รูปที่ 15 รูปหัวใจพร้อมพิกัด

ในตอนนี้ รูปร่างสามารถกำหนดได้ง่ายกว่าเดิมด้วยการระบุมุม (apis) และ รัศมีจากศูนย์กลางในแต่ละจุด:

วันที่ รูปหัวใจ
รูปที่ 16 รูปหัวใจพร้อมพิกัด โดยไม่ต้องปัดเศษ

ตอนนี้สามารถสร้างจุดยอดและส่งไปยังฟังก์ชัน 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)
)

ซึ่งส่งผลให้หัวใจสีชมพู:

วันที่ รูปหัวใจ
รูปที่ 17 แล้วก็ผลการค้นหารูปหัวใจ

หากรูปทรงก่อนหน้าไม่ครอบคลุม Use Case ของคุณ ให้พิจารณาใช้ Path คลาสในการวาด รูปร่าง หรือการโหลด ImageVector ไฟล์จาก ของ Google ไลบรารี graphics-shapes ไม่ได้มีจุดประสงค์เพื่อการใช้แบบกำหนดเอง รูปทรงต่างๆ แต่มีจุดประสงค์โดยเฉพาะเพื่อให้สร้างรูปเหลี่ยมทรงกลมได้ง่ายและ สร้างภาพเคลื่อนไหวระหว่างภาพเหล่านั้น

แหล่งข้อมูลเพิ่มเติม

สำหรับข้อมูลเพิ่มเติมและตัวอย่าง โปรดดูแหล่งข้อมูลต่อไปนี้