You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在Android Jetpack Compose中实现带Notch与Cutout的票券式布局

在Android Jetpack Compose中实现带Notch与Cutout的票券式布局

嗨~作为Compose新手,想要做这种带缺口和镂空的票券布局其实完全不用慌,咱们从最基础的核心思路开始一步步来!

核心思路

Compose里实现这种自定义形状的布局,关键就是自定义Shape(形状)——票券的顶部Notch、侧边Cutout都是形状的一部分,再配合阴影Modifier就能做出你想要的质感效果。

第一步:自定义票券形状

先创建一个继承自Shape的类,通过Path来绘制带缺口的完整轮廓。你可以根据需求调整缺口的大小参数:

class TicketShape(
    private val notchRadius: Dp = 16.dp,
    private val cutoutRadius: Dp = 24.dp
) : Shape {
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        val notchRadiusPx = with(density) { notchRadius.toPx() }
        val cutoutRadiusPx = with(density) { cutoutRadius.toPx() }
        val path = Path().apply {
            // 从顶部左侧缺口的右侧开始绘制
            moveTo(notchRadiusPx, 0f)
            
            // 绘制顶部直线到右侧缺口左侧
            lineTo(size.width - notchRadiusPx, 0f)
            // 绘制顶部右侧的凹形Notch
            arcTo(
                rect = Rect(
                    left = size.width - 2 * notchRadiusPx,
                    top = -notchRadiusPx,
                    right = size.width,
                    bottom = notchRadiusPx
                ),
                startAngleDegrees = 90f,
                sweepAngleDegrees = 180f,
                forceMoveTo = false
            )
            
            // 绘制右侧直线到底部Cutout上方
            lineTo(size.width, size.height - cutoutRadiusPx)
            // 绘制右下角的Cutout缺口
            arcTo(
                rect = Rect(
                    left = size.width - cutoutRadiusPx,
                    top = size.height - 2 * cutoutRadiusPx,
                    right = size.width + cutoutRadiusPx,
                    bottom = size.height
                ),
                startAngleDegrees = 0f,
                sweepAngleDegrees = 180f,
                forceMoveTo = false
            )
            
            // 绘制底部直线到左侧Cutout右侧
            lineTo(cutoutRadiusPx, size.height)
            // 绘制左下角的Cutout缺口
            arcTo(
                rect = Rect(
                    left = -cutoutRadiusPx,
                    top = size.height - 2 * cutoutRadiusPx,
                    right = cutoutRadiusPx,
                    bottom = size.height
                ),
                startAngleDegrees = 0f,
                sweepAngleDegrees = -180f,
                forceMoveTo = false
            )
            
            // 绘制左侧直线到顶部Notch下方
            lineTo(0f, notchRadiusPx)
            // 绘制顶部左侧的凹形Notch
            arcTo(
                rect = Rect(
                    left = 0f,
                    top = -notchRadiusPx,
                    right = 2 * notchRadiusPx,
                    bottom = notchRadiusPx
                ),
                startAngleDegrees = 270f,
                sweepAngleDegrees = 180f,
                forceMoveTo = false
            )
            
            close() // 闭合路径
        }
        return Outline.Generic(path)
    }
}

第二步:使用自定义形状并添加阴影

接下来就可以在布局里用这个形状啦,配合Modifier.shadow()让阴影跟着票券形状走,再加上背景色和内部内容:

@Composable
fun TicketLayout() {
    // 实例化自定义形状,可调整缺口大小
    val ticketShape = TicketShape(notchRadius = 16.dp, cutoutRadius = 20.dp)
    
    Box(
        modifier = Modifier
            .size(width = 300.dp, height = 400.dp)
            // 添加阴影,阴影形状和票券形状保持一致
            .shadow(
                elevation = 8.dp,
                shape = ticketShape,
                spotColor = Color(0x40000000),
                ambientColor = Color(0x40000000)
            )
            // 设置白色背景,同样使用自定义形状
            .background(Color.White, shape = ticketShape)
            .padding(16.dp)
    ) {
        // 这里放票券内部的内容,比如标题、详情等
        Column(
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier.fillMaxSize()
        ) {
            Text("我的电影票", fontSize = 20.sp, fontWeight = FontWeight.Bold)
            Spacer(modifier = Modifier.height(16.dp))
            Text("场次:2024-05-20 19:30", fontSize = 14.sp)
            Spacer(modifier = Modifier.height(8.dp))
            Text("座位:5排12座", fontSize = 14.sp)
        }
    }
}

新手小提示

  • 要是对Path的坐标计算有点懵,你可以把每一段lineToarcTo的步骤拆开来,单独看每一段的绘制效果,慢慢就有手感啦~
  • 要是需要中间的撕痕式镂空,只需要在自定义Shape的Path里,在票券中间的位置再添加一组半圆缺口就行,方法和侧边的Cutout完全一样。
  • 调整阴影的elevation值可以改变阴影的深浅和扩散范围,多试几次就能找到你想要的质感~

内容来源于stack exchange

火山引擎 最新活动