简体   繁体   English

Jetpack Compose - 边框上带有弧形的 CardView

[英]Jetpack Compose - CardView with Arc Shape on border

I'm trying to achieve below cardview arc shape on cardview border/stroke.我正在尝试在 cardview 边框/笔划上实现低于 cardview 弧形。 Already tried to search on google but didn't find any relevant answer that suits with requirement.已经尝试在谷歌上搜索但没有找到任何符合要求的相关答案。

Any lead or help will be appriciated.任何线索或帮助都将得到应用。

在此处输入图像描述

Answer from Cirilo Bido and Raghunandan is good place to start, you round corners of rectangle with arcTo but you can't draw curved edges on top of clipped out shape. Cirilo Bido 和 Raghunandan 的回答是一个很好的起点,您可以使用arcTo将矩形的角倒圆角,但是您不能在裁剪形状的顶部绘制弯曲的边缘。 You need to use cubicTo to draw rounded edge and curve to clip out bottom shape您需要使用cubicTo绘制圆角边缘和曲线以裁剪底部形状

   val shape = GenericShape {size: Size, layoutDirection: LayoutDirection ->
                            // draw cubic on left and right sides for button space
                            cubicTo()
                        }

在此处输入图像描述

You can check out this answer for drawing with cubic to.您可以查看此答案以使用立方体绘制。 By combining both you can draw that path.通过结合两者,您可以绘制该路径。

Jetpack Compose: How to draw a path / line like this Jetpack Compose:如何绘制这样的路径/线

I created this path based on article shared by我根据分享的文章创建了这条路径
Raghunandan initially, even though that is amazing answer for animating BottomBar it doesn't create a rounded shape if you look closely, at the bottom it's creating a triangular shape at the bottom instead of rounded one and shape OP requires and in article is also different. Raghunandan 最初,即使这是动画 BottomBar 的惊人答案,如果您仔细观察它也不会创建圆形,在底部它会在底部创建三角形而不是圆形,并且 OP 需要形状并且在文章中也是不同的.

So i used sliders to create bezier from the link i shared above.所以我使用滑块从上面共享的链接创建贝塞尔曲线。 It's available as tutorial here too.它也可以在这里作为教程使用。 Still it can be tweaked to more precise shape if you wish to.如果您愿意,它仍然可以调整为更精确的形状。

在此处输入图像描述

I used x0, y0 as reference point to set control points and created this Path extension function.我使用 x0, y0 作为参考点来设置控制点并创建了这个路径扩展 function。

fun Path.roundedRectanglePath(
    size: Size,
    cornerRadius: Float,
    fabRadius: Float,
) {

    val centerX = size.width / 2
    val x0 = centerX - fabRadius * 1.15f
    val y0 = 0f

    // offset of the first control point (top part)
    val topControlX = x0 + fabRadius * .5f
    val topControlY = y0

    // offset of the second control point (bottom part)
    val bottomControlX = x0
    val bottomControlY = y0 + fabRadius

    // first curve
    // set the starting point of the curve (P2)
    val firstCurveStart = Offset(x0, y0)

    // set the end point for the first curve (P3)
    val firstCurveEnd = Offset(centerX, fabRadius * 1f)

    // set the first control point (C1)
    val firstCurveControlPoint1 = Offset(
        x = topControlX,
        y = topControlY
    )

    // set the second control point (C2)
    val firstCurveControlPoint2 = Offset(
        x = bottomControlX,
        y = bottomControlY
    )


    // second curve
    // end of first curve and start of second curve is the same (P3)
    val secondCurveStart = Offset(
        x = firstCurveEnd.x,
        y = firstCurveEnd.y
    )

    // end of the second curve (P4)
    val secondCurveEnd = Offset(
        x = centerX + fabRadius * 1.15f,
        y = 0f
    )

    // set the first control point of second curve (C4)
    val secondCurveControlPoint1 = Offset(
        x = secondCurveStart.x + fabRadius,
        y = bottomControlY
    )

    // set the second control point (C3)
    val secondCurveControlPoint2 = Offset(
        x = secondCurveEnd.x - fabRadius / 2,
        y = topControlY
    )


    // Top left arc
    val radius = cornerRadius * 2

    arcTo(
        rect = Rect(
            left = 0f,
            top = 0f,
            right = radius,
            bottom = radius
        ),
        startAngleDegrees = 180.0f,
        sweepAngleDegrees = 90.0f,
        forceMoveTo = false
    )



    lineTo(x = firstCurveStart.x, y = firstCurveStart.y)

    // bezier curve with (P2, C1, C2, P3)
    cubicTo(
        x1 = firstCurveControlPoint1.x,
        y1 = firstCurveControlPoint1.y,
        x2 = firstCurveControlPoint2.x,
        y2 = firstCurveControlPoint2.y,
        x3 = firstCurveEnd.x,
        y3 = firstCurveEnd.y
    )

    // bezier curve with (P3, C4, C3, P4)
    cubicTo(
        x1 = secondCurveControlPoint1.x,
        y1 = secondCurveControlPoint1.y,
        x2 = secondCurveControlPoint2.x,
        y2 = secondCurveControlPoint2.y,
        x3 = secondCurveEnd.x,
        y3 = secondCurveEnd.y
    )

    lineTo(x = size.width - cornerRadius, y = 0f)

    // Top right arc
    arcTo(
        rect = Rect(
            left = size.width - radius,
            top = 0f,
            right = size.width,
            bottom = radius
        ),
        startAngleDegrees = -90.0f,
        sweepAngleDegrees = 90.0f,
        forceMoveTo = false
    )

    lineTo(x = 0f + size.width, y = size.height - cornerRadius)

    // Bottom right arc
    arcTo(
        rect = Rect(
            left = size.width - radius,
            top = size.height - radius,
            right = size.width,
            bottom = size.height
        ),
        startAngleDegrees = 0f,
        sweepAngleDegrees = 90.0f,
        forceMoveTo = false
    )

    lineTo(x = cornerRadius, y = size.height)

    // Bottom left arc
    arcTo(
        rect = Rect(
            left = 0f,
            top = size.height - radius,
            right = radius,
            bottom = size.height
        ),
        startAngleDegrees = 90.0f,
        sweepAngleDegrees = 90.0f,
        forceMoveTo = false
    )

    lineTo(x = 0f, y = cornerRadius)
    close()
}

Composable that uses this shape使用此形状的可组合项

@Composable
private fun CustomArcShape(
    modifier: Modifier,
    elevation: Dp = 4.dp,
    color: Color = MaterialTheme.colorScheme.surface,
    contentColor: Color = contentColorFor(color),
    content: @Composable () -> Unit
) {

    val diameter = 60.dp
    val radiusDp = diameter / 2

    val cornerRadiusDp = 10.dp

    val density = LocalDensity.current
    val cutoutRadius = density.run { radiusDp.toPx() }
    val cornerRadius = density.run { cornerRadiusDp.toPx() }

    val shape = remember {
        GenericShape { size: Size, layoutDirection: LayoutDirection ->
            this.roundedRectanglePath(
                size = size,
                cornerRadius = cornerRadius,
                fabRadius = cutoutRadius * 2
            )
        }
    }

    Spacer(modifier = Modifier.height(diameter / 2))

    Box(contentAlignment = Alignment.TopCenter) {
        FloatingActionButton(
            shape = CircleShape,
            containerColor = Color(0xffD32F2F),
            modifier = Modifier
                .offset(y = -diameter / 5)
                .size(diameter)
                .drawBehind {
                    drawCircle(
                        Color.Red.copy(.5f),
                        radius = 1.3f * size.width / 2
                    )

                    drawCircle(
                        Color.Red.copy(.3f),
                        radius = 1.5f * size.width / 2
                    )

                }
                .align(Alignment.TopCenter),
            onClick = { /*TODO*/ }
        ) {
            Icon(
                tint = Color.White,
                imageVector = Icons.Filled.Close,
                contentDescription = "Close"
            )
        }

        Surface(
            modifier = modifier,
            shape = shape,
            shadowElevation = elevation,
            color = color,
            contentColor = contentColor
        ) {
            Column {
                Spacer(modifier = Modifier.height(diameter))
                content()

            }
        }
    }
}

And demonstration及示范

@Composable
private fun CustomArcShapeSample() {
    Column(
        modifier = Modifier
            .fillMaxSize()
    ) {

        CustomArcShape(
            modifier = Modifier
                .padding(10.dp)
                .fillMaxWidth()
                .height(250.dp)
        ) {
            Column(
                modifier = Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(
                    "Payment Failed",
                    color = MaterialTheme.colorScheme.error,
                    fontWeight = FontWeight.Bold
                )
                Spacer(modifier = Modifier.height(10.dp))
                Text("Sorry !", fontSize = 24.sp, fontWeight = FontWeight.Bold)
                Spacer(modifier = Modifier.height(10.dp))
                Text("Your transfer to bank failed", color = Color.LightGray)
            }
        }

        Spacer(modifier = Modifier.height(40.dp))

        CustomArcShape(
            modifier = Modifier
                .padding(10.dp)
                .fillMaxWidth()
                .height(250.dp)
        ) {
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .border(1.dp, Color.Green),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(
                    "Payment Failed",
                    color = MaterialTheme.colorScheme.error,
                    fontWeight = FontWeight.Bold
                )
                Spacer(modifier = Modifier.height(10.dp))
                Text("Sorry !", fontSize = 24.sp, fontWeight = FontWeight.Bold)
                Spacer(modifier = Modifier.height(10.dp))
                Text("Your transfer to bank failed", color = Color.LightGray)
            }
        }

    }
}

You probably need to draw that arc in a custom composable, I found this article that can help you to understand the process of drawing in compose!您可能需要在自定义可组合项中绘制该弧线,我发现这篇文章可以帮助您了解在 compose 中绘制的过程!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM