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.
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.
@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.