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
相关产品推荐
相关产品推荐

