You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose:如何在票券顶部弧形区域放置圆形图片?

票券顶部弧形区域嵌入圆形图片的实现方案

问题背景

已通过自定义Shape实现票券样式背景,代码如下:

class TicketShapeBackground(
    private val circleRadius: Dp,
    private val cornerSize: CornerSize
) : Shape {

    override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline {
        return Outline.Generic(path = getPath(size, density))
    }

    private fun getPath(size: Size, density: Density): Path {
        val roundedRect = RoundRect(size.toRect(), CornerRadius(cornerSize.toPx(size, density)))
        val roundedRectPath = Path().apply { addRoundRect(roundedRect) }
        return Path.combine(operation = PathOperation.Intersect, path1 = roundedRectPath, path2 = getTicketPath(size, density))
    }

    private fun getTicketPath(size: Size, density: Density): Path {
        val middleX = size.width.div(other = 2)
        val middleY = size.height.div(other = 2)
        val circleRadiusInPx = with(density) { circleRadius.toPx() }
        return Path().apply {
            reset()
           
            lineTo(x = 0F, y = 0F)
       
            lineTo(middleX, y = 0F)

            arcTo(
                rect = Rect(
                    left = middleX.minus(circleRadiusInPx),
                    top = 0F.minus(circleRadiusInPx),
                    right = middleX.plus(circleRadiusInPx),
                    bottom = circleRadiusInPx
                ),
                startAngleDegrees = 180F,
                sweepAngleDegrees = -180F,
                forceMoveTo = false
            )
            // Draw line to top right
            lineTo(x = size.width, y = 0F)
           
            lineTo(x = size.width, y = middleY)

            arcTo(
                rect = Rect(
                    left = size.width - circleRadiusInPx,
                    top = middleY - circleRadiusInPx,
                    right = size.width + circleRadiusInPx,
                    bottom = middleY + circleRadiusInPx
                ),
                startAngleDegrees = 270f,
                sweepAngleDegrees = -180F,
                forceMoveTo = false
            )
            lineTo(x = size.width, y = size.height)

            lineTo(middleX, y = size.height)
           
            lineTo(x = 0F, y = size.height)

            arcTo(
                rect = Rect(
                    left = 0F - circleRadiusInPx,
                    top = middleY - circleRadiusInPx,
                    right = circleRadiusInPx,
                    bottom = middleY + circleRadiusInPx
                ),
                startAngleDegrees = 90f,
                sweepAngleDegrees = -180F,
                forceMoveTo = false
            )

            lineTo(x = 0F, y = 0F)
        }
    }
}

使用方式:

Box(
    Modifier.clip(TicketShapeBackground(34.dp, CornerSize(5.dp)))
        .fillMaxSize()
        .background(Color.Red)
) { }

现在需要在顶部中间的弧形区域内嵌入一张圆形图片,不确定用Box的具体实现方式,希望得到简洁方案。

解决方案

1. 优化自定义Shape路径(可选但推荐)

原getTicketPath存在冗余lineTo代码,可能导致路径错误,先清理并补全对称弧形:

private fun getTicketPath(size: Size, density: Density): Path {
    val middleX = size.width / 2
    val middleY = size.height / 2
    val circleRadiusInPx = with(density) { circleRadius.toPx() }
    return Path().apply {
        reset()
        // 左上角 -> 顶部中间
        moveTo(0F, 0F)
        lineTo(middleX, 0F)
        // 顶部中间外凹弧
        arcTo(
            rect = Rect(
                left = middleX - circleRadiusInPx,
                top = -circleRadiusInPx,
                right = middleX + circleRadiusInPx,
                bottom = circleRadiusInPx
            ),
            startAngleDegrees = 180F,
            sweepAngleDegrees = -180F,
            forceMoveTo = false
        )
        // 顶部弧右端 -> 右上角 -> 右侧中间
        lineTo(size.width, 0F)
        lineTo(size.width, middleY)
        // 右侧中间外凹弧
        arcTo(
            rect = Rect(
                left = size.width - circleRadiusInPx,
                top = middleY - circleRadiusInPx,
                right = size.width + circleRadiusInPx,
                bottom = middleY + circleRadiusInPx
            ),
            startAngleDegrees = 270F,
            sweepAngleDegrees = -180F,
            forceMoveTo = false
        )
        // 右侧弧下端 -> 右下角 -> 底部中间
        lineTo(size.width, size.height)
        lineTo(middleX, size.height)
        // 底部中间外凹弧(补全对称)
        arcTo(
            rect = Rect(
                left = middleX - circleRadiusInPx,
                top = size.height - circleRadiusInPx,
                right = middleX + circleRadiusInPx,
                bottom = size.height + circleRadiusInPx
            ),
            startAngleDegrees = 0F,
            sweepAngleDegrees = -180F,
            forceMoveTo = false
        )
        // 底部弧左端 -> 左下角 -> 左侧中间
        lineTo(0F, size.height)
        lineTo(0F, middleY)
        // 左侧中间外凹弧
        arcTo(
            rect = Rect(
                left = -circleRadiusInPx,
                top = middleY - circleRadiusInPx,
                right = circleRadiusInPx,
                bottom = middleY + circleRadiusInPx
            ),
            startAngleDegrees = 90F,
            sweepAngleDegrees = -180F,
            forceMoveTo = false
        )
        // 闭合路径
        lineTo(0F, 0F)
    }
}

2. 嵌入圆形图片的实现

通过嵌套Box,利用绝对定位快速将圆形图片嵌入顶部弧形:

// 提取弧形半径常量,方便复用
val ticketCircleRadius = 34.dp

Box(
    modifier = Modifier
        .fillMaxSize()
        .clip(TicketShapeBackground(ticketCircleRadius, CornerSize(5.dp)))
        .background(Color.Red)
) {
    // 顶部弧形内的圆形图片
    Image(
        painter = painterResource(id = R.drawable.your_circular_image),
        contentDescription = "Ticket top icon",
        modifier = Modifier
            .size(ticketCircleRadius * 2) // 图片直径匹配弧形直径
            .align(Alignment.TopCenter)
            .offset(y = -(ticketCircleRadius / 2)) // 向上偏移半个半径,刚好嵌入弧形
            .clip(CircleShape) // 确保图片显示为圆形
    )
}

3. 方案说明

  • 用align(Alignment.TopCenter)快速定位到顶部中间,结合offset调整y轴位置,让图片一半在票券外、一半在内,完美嵌入弧形区域。
  • 图片大小设置为弧形直径,保证完全填充弧形空间。
  • 借助CircleShape裁剪图片,确保显示形态符合需求。

内容的提问来源于stack exchange,提问作者Pritish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 16:50:40