รูปร่างใน Compose

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

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

หากต้องการสร้างรูปหลายเหลี่ยมที่โค้งมนที่กำหนดเองในคอมโพซ ให้เพิ่มข้อกำหนดของ graphics-shapes ลงใน app/build.gradle

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

คลังนี้ช่วยให้คุณสร้างรูปร่างจากรูปหลายเหลี่ยมได้ แม้ว่ารูปทรงหลายเหลี่ยมจะมีเพียงขอบตรงและมุมแหลม แต่รูปทรงเหล่านี้ก็อนุญาตให้มีมุมโค้งมนได้ ซึ่งช่วยให้คุณเปลี่ยนรูปร่างระหว่าง 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 ซึ่งเก็บเรขาคณิตที่แสดงถึงรูปร่างที่ขอ หากต้องการวาดรูปร่างนั้นในแอป Compose คุณต้องรับออบเจ็กต์ Path เพื่อสร้างรูปร่างนั้นในแบบฟอร์มที่ Compose จะรู้วิธีวาด

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

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

รัศมี

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 1 รายการที่ใช้รูปร่างทั้ง 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 ในการเขียนเพื่อเปลี่ยนลักษณะการแสดงผล 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)
    )
}

การดำเนินการนี้ส่งผลให้เกิดผลดังต่อไปนี้

รูปหกเหลี่ยมที่มีข้อความ "hello compose" อยู่ตรงกลาง
รูปที่ 10 รูปหกเหลี่ยมที่มีข้อความ "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)

    )
}

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

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

คุณสามารถใช้ไลบรารี graphics-shape เพื่อสร้างปุ่มที่เปลี่ยนรูปร่างระหว่าง 2 รูปแบบเมื่อกด ก่อนอื่น ให้สร้าง MorphPolygonShape ที่ขยาย Shape โดยปรับขนาดและแปลให้พอดี โปรดสังเกตการผ่านค่าของ 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)
    }
}

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

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

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