Home > database >  Add a border for top half of a card in compose with corner radius
Add a border for top half of a card in compose with corner radius

Time:12-11

I want to add a border to the top half of the Card component that has a corner radius (10dp). So, only the bottom part is missing rest of the card has a stroke of 1dp. (kind of like U and inverted U)

And I want to do the same for card that has a bottom corner radius and the top part is missing.

I tried to design a custom shape but it's not respecting the shape of the card.

// this is just a line but it doesn't respect the card corner radius? 

private fun createShape(thickness: Float) : Shape {
    return GenericShape { size, _ ->
        moveTo(0f,0f)
        lineTo(0f, size.height)
        lineTo(thickness, size.height)
        lineTo(thickness, 0f)
        lineTo(0f, thickness)
    }
}


val thickness = with(LocalDensity.current) {
    1.dp.toPx()
}
Card(
    shape = RoundedCornerShape(topEnd = 10.dp, topStart = 10.dp, bottomEnd = 0.dp, bottomStart = 0.dp),
    modifier = Modifier
        .border(BorderStroke(width = 1.dp, color = Color.Black), createShape(thickness))

) {
    ...
}

CodePudding user response:

Border doesn't seem to allow open shapes, forces shape to be closed even if you draw 3 lines because of that you need to use Modifier.drawBehind or Modifier.drawWithContent.

enter image description here

Created a Modifier that draws u shaped border as

fun Modifier.semiBorder(strokeWidth: Dp, color: Color, cornerRadiusDp: Dp) = composed(
    factory = {
        val density = LocalDensity.current
        val strokeWidthPx = density.run { strokeWidth.toPx() }
        val cornerRadius = density.run { cornerRadiusDp.toPx() }

        Modifier.drawBehind {
            val width = size.width
            val height = size.height

            drawLine(
                color = color,
                start = Offset(x = 0f, y = height),
                end = Offset(x = 0f, y = cornerRadius),
                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 = cornerRadius, 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 = height),
                end = Offset(x = width, y = cornerRadius),
                strokeWidth = strokeWidthPx
            )
        }
    }
)

Usage

@Composable
private fun UShapeBorderSample() {
    Card(
        shape = RoundedCornerShape(
            topEnd = 10.dp,
            topStart = 10.dp,
            bottomEnd = 0.dp,
            bottomStart = 0.dp
        ),
        modifier = Modifier
            .semiBorder(1.dp, Color.Black, 10.dp)

    ) {
        Box(
            modifier = Modifier
                .size(150.dp)
                .background(Color.White),
            contentAlignment = Alignment.Center
        ) {
            Text("Hello World")
        }
    }

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

    Card(
        shape = RoundedCornerShape(
            topEnd = 20.dp,
            topStart = 20.dp,
            bottomEnd = 0.dp,
            bottomStart = 0.dp
        ),
        modifier = Modifier
            .semiBorder(1.dp, Color.Black, 20.dp)

    ) {
        Box(
            modifier = Modifier
                .size(150.dp)
                .background(Color.White),
            contentAlignment = Alignment.Center
        ) {
            Text("Hello World")
        }
    }
}

You need to use arc to create rounded corners. And when creating shape you don't pass thickness but radius of shape. Thickness is required when drawing border. What you draw is a rectangle with 1.dp width.

enter image description here

@Composable
private fun createShape(cornerRadius: Dp): Shape {

    val density = LocalDensity.current
    return GenericShape { size, _ ->

        val width = size.width
        val height = size.height
        val cornerRadiusPx = density.run { cornerRadius.toPx() }

        moveTo(0f, height)

        // Vertical line on left size
        lineTo(0f, cornerRadiusPx * 2)

        arcTo(
            rect = Rect(
                offset = Offset.Zero,
                size = Size(cornerRadiusPx * 2, cornerRadiusPx * 2)
            ),
            startAngleDegrees = 180f,
            sweepAngleDegrees = 90f,
            forceMoveTo = false
        )

        lineTo(width - cornerRadiusPx * 2, 0f)
        arcTo(
            rect = Rect(
                offset = Offset(width - cornerRadiusPx * 2, 0f),
                size = Size(cornerRadiusPx * 2, cornerRadiusPx * 2)
            ),
            startAngleDegrees = 270f,
            sweepAngleDegrees = 90f,
            forceMoveTo = false
        )
        // Vertical line on right size
        lineTo(width, height)

    }
}

Usage

@Composable
private fun UShapeBorderSample() {
    Card(
        shape = RoundedCornerShape(
            topEnd = 10.dp,
            topStart = 10.dp,
            bottomEnd = 0.dp,
            bottomStart = 0.dp
        ),
        modifier = Modifier
            .border(BorderStroke(width = 1.dp, color = Color.Black), createShape(10.dp))

    ) {
        Box(modifier = Modifier
            .size(200.dp)
            .background(Color.White),
            contentAlignment = Alignment.Center
        ) {
            Text("Hello World")
        }
    }
}

Border doesn't respect shape because it's a drawing but Card is a Box under the hood that uses shape with Modifier.clip() which itself is Modifier.graphicsLayer{clip} that applies operations on a layer.

You can check out this answer about clip and border for the difference.

https://stackoverflow.com/a/73091667/5457853

  • Related