รูปร่างใน Compose

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

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

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

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

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

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

ข้อมูลโค้ดต่อไปนี้สร้างรูปร่างรูปหลายเหลี่ยมพื้นฐานที่มี 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) จะแสดงเป็นวงกลมเท่านั้น มุมโค้งมน ปัจจัยการปรับให้เรียบที่ไม่ใช่ 0 (สูงสุด 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 รายการนี้ คุณสามารถใช้ Animation 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 รูปหกเหลี่ยมที่มีข้อความ "Hello Compose" อยู่ตรงกลาง

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

ปุ่มเปลี่ยนรูปแบบเมื่อคลิก

คุณสามารถใช้ไลบรารี 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))
}

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

การใช้การเปลี่ยนรูปแบบเป็นคลิกระหว่าง 2 รูปร่าง
รูปที่ 12 ใช้การเปลี่ยนรูปแบบเป็นคลิกระหว่าง 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 รูปหัวใจพร้อมพิกัด

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

รูปหัวใจ
รูปที่ 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 แล้วก็ผลการค้นหารูปหัวใจ

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

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

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