Jetpack Compose 部分或开放式边框

6

我想绘制部分或单边打开的圆角矩形边框以实现这种效果:

enter image description here

尝试了一下后,我得到了以下结果:

enter image description here

这是通过以下方式完成的:

RoundedCornerShape(topStartPercent = 50, bottomStartPercent = 50) // start
RoundedCornerShape(topEndPercent = 50, bottomEndPercent = 50) // end
RectangleShape // for middle

我想要的是删除单元格之间的垂直连接线。
2个回答

10
您可以使用Modifier.drawBehind在组合项后面绘制边框,并检查您的组合项是否位于起始、中心或结束位置。
enum class BorderOrder {
    Start, Center, End
}

fun Modifier.drawSegmentedBorder(
    strokeWidth: Dp,
    color: Color,
    cornerPercent: Int,
    borderOrder: BorderOrder,
    drawDivider: Boolean = false
) = composed(
    factory = {

        val density = LocalDensity.current
        val strokeWidthPx = density.run { strokeWidth.toPx() }

        Modifier.drawBehind {
            val width = size.width
            val height = size.height
            val cornerRadius = height * cornerPercent / 100

            when (borderOrder) {
                BorderOrder.Start -> {

                    drawLine(
                        color = color,
                        start = Offset(x = width, y = 0f),
                        end = Offset(x = cornerRadius, y = 0f),
                        strokeWidth = strokeWidthPx
                    )

                    // Top left arc
                    drawArc(
                        color = color,
                        startAngle = 180f,
                        sweepAngle = 90f,
                        useCenter = false,
                        topLeft = Offset.Zero,
                        size = Size(cornerRadius * 2, cornerRadius * 2),
                        style = Stroke(width = strokeWidthPx)
                    )
                    drawLine(
                        color = color,
                        start = Offset(x = 0f, y = cornerRadius),
                        end = Offset(x = 0f, y = height - cornerRadius),
                        strokeWidth = strokeWidthPx
                    )
                    // Bottom left arc
                    drawArc(
                        color = color,
                        startAngle = 90f,
                        sweepAngle = 90f,
                        useCenter = false,
                        topLeft = Offset(x = 0f, y = height - 2 * cornerRadius),
                        size = Size(cornerRadius * 2, cornerRadius * 2),
                        style = Stroke(width = strokeWidthPx)
                    )
                    drawLine(
                        color = color,
                        start = Offset(x = cornerRadius, y = height),
                        end = Offset(x = width, y = height),
                        strokeWidth = strokeWidthPx
                    )
                }
                BorderOrder.Center -> {
                    drawLine(
                        color = color,
                        start = Offset(x = 0f, y = 0f),
                        end = Offset(x = width, y = 0f),
                        strokeWidth = strokeWidthPx
                    )
                    drawLine(
                        color = color,
                        start = Offset(x = 0f, y = height),
                        end = Offset(x = width, y = height),
                        strokeWidth = strokeWidthPx
                    )

                    if (drawDivider) {
                        drawLine(
                            color = color,
                            start = Offset(x = 0f, y = 0f),
                            end = Offset(x = 0f, y = height),
                            strokeWidth = strokeWidthPx
                        )
                    }
                }
                else -> {

                    if (drawDivider) {
                        drawLine(
                            color = color,
                            start = Offset(x = 0f, y = 0f),
                            end = Offset(x = 0f, y = height),
                            strokeWidth = strokeWidthPx
                        )
                    }

                    drawLine(
                        color = color,
                        start = Offset(x = 0f, y = 0f),
                        end = Offset(x = width - cornerRadius, y = 0f),
                        strokeWidth = strokeWidthPx
                    )

                    // Top right arc
                    drawArc(
                        color = color,
                        startAngle = 270f,
                        sweepAngle = 90f,
                        useCenter = false,
                        topLeft = Offset(x = width - cornerRadius * 2, y = 0f),
                        size = Size(cornerRadius * 2, cornerRadius * 2),
                        style = Stroke(width = strokeWidthPx)
                    )
                    drawLine(
                        color = color,
                        start = Offset(x = width, y = cornerRadius),
                        end = Offset(x = width, y = height - cornerRadius),
                        strokeWidth = strokeWidthPx
                    )
                    // Bottom right arc
                    drawArc(
                        color = color,
                        startAngle = 0f,
                        sweepAngle = 90f,
                        useCenter = false,
                        topLeft = Offset(
                            x = width - 2 * cornerRadius,
                            y = height - 2 * cornerRadius
                        ),
                        size = Size(cornerRadius * 2, cornerRadius * 2),
                        style = Stroke(width = strokeWidthPx)
                    )
                    drawLine(
                        color = color,
                        start = Offset(x = 0f, y = height),
                        end = Offset(x = width -cornerRadius, y = height),
                        strokeWidth = strokeWidthPx
                    )
                }
            }
        }
    }
)

使用方法

@Composable
private fun SegmentedBorderSample() {
    Row {
        repeat(3) {

            val order = when (it) {
                0 -> BorderOrder.Start
                2 -> BorderOrder.End
                else -> BorderOrder.Center
            }

            Box(
                contentAlignment = Alignment.Center,
                modifier = Modifier
                    .size(40.dp)
                    .drawSegmentedBorder(
                        strokeWidth = 2.dp,
                        color = Color.Green,
                        borderOrder = order,
                        cornerPercent = 40,
                        drawDivider = false
                    )
                    .padding(4.dp)
            ) {
                Text(text = "$it")
            }
        }
    }


    Row {
        repeat(4) {

            val order = when (it) {
                0 -> BorderOrder.Start
                3 -> BorderOrder.End
                else -> BorderOrder.Center
            }

            Box(
                contentAlignment = Alignment.Center,
                modifier = Modifier
                    .size(40.dp)
                    .drawSegmentedBorder(
                        strokeWidth = 2.dp,
                        color = Color.Cyan,
                        borderOrder = order,
                        cornerPercent = 50,
                        drawDivider = true
                    )
                    .padding(4.dp)
            ) {
                Text(text = "$it")
            }
        }
    }
}

结果

在此输入图片描述


好的,谢谢哥们儿 :) - wojciech_maciejewski
好的,谢谢哥们儿 :) - undefined
1
欢迎您。通过Modifier.drawBehind的特性,它会在您的Composable之后绘制。如果您希望边框绘制在具有背景的组合上方,例如使用Modifier.drawWithContent{drawContent() // rest of the drawing - Thracian

2
您可以使用offset修饰符来避免双重边框:
类似这样的内容:
val itemsList = (0..4).toList()

Row() {
    itemsList.forEachIndexed { index, item ->
        OutlinedButton(
            onClick = { /** do something */},
            modifier = when (index) {
                0 ->
                    Modifier
                        .offset(0.dp, 0.dp)
                else ->
                    Modifier
                        .offset((-1 * index).dp, 0.dp)
            },
            shape = when (index) {
                // left outer button
                0 -> RoundedCornerShape(topStart = cornerRadius, topEnd = 0.dp, bottomStart = cornerRadius, bottomEnd = 0.dp)
                // right outer button
                itemsList.size - 1 -> RoundedCornerShape(topStart = 0.dp, topEnd = cornerRadius, bottomStart = 0.dp, bottomEnd = cornerRadius)
                // middle button
                else -> RoundedCornerShape(0.dp)
            },
            border = BorderStroke(1.dp, Blue500)
        ) {}
    }
}

enter image description here


offset是什么?有没有相关文章可供参考?我对此没有经验。我想了解一下offset的工作原理。 - Kotlin Learner
@vivekmodi Modifier.offset 改变了 Composable 或该修饰符之后的交互位置。这个答案将项目从原始位置移动 1.dp,如果边框描边更大,则更明显。当您有其他可用作参考的项目时,这不是期望的结果。 - Thracian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接