Skip to content

Instantly share code, notes, and snippets.

@mightyfrog
Last active October 2, 2025 12:23
Show Gist options
  • Select an option

  • Save mightyfrog/57ae8c47af4ed5ae042b515b71055968 to your computer and use it in GitHub Desktop.

Select an option

Save mightyfrog/57ae8c47af4ed5ae042b515b71055968 to your computer and use it in GitHub Desktop.
RoundedCornerHorizontalDivider
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* A HorizontalDivider that matches the bottom border of a box with rounded corners.
* It draws a straight bottom line with quarter-circle arcs at both ends.
*/
@Composable
fun RoundedCornerHorizontalDivider(
modifier: Modifier = Modifier,
thickness: Dp = Dp.Hairline,
color: Color = MaterialTheme.colorScheme.outlineVariant,
cornerRadius: Dp = 16.dp,
upward: Boolean = true,
gradientSolidPortion: Float? = null, // Changed from gradientEnabled
) {
val effectiveThickness = if (thickness == Dp.Hairline) 1.dp else thickness
val canvasHeight = (cornerRadius + effectiveThickness)
Canvas(
modifier
.fillMaxWidth()
.height(canvasHeight),
) {
val widthPx = size.width
if (widthPx <= 0f) return@Canvas
val strokeWidth = effectiveThickness.toPx()
val radius = cornerRadius.toPx().coerceAtLeast(0f).coerceAtMost(widthPx / 2f)
// Place the baseline inside the canvas depending on direction
val bottomY = if (upward) {
size.height - strokeWidth / 2f
} else {
strokeWidth / 2f
}
val centerYOffset = if (upward) -radius else +radius
val centerY = bottomY + centerYOffset
val leftRectTop = centerY - radius
val leftRectBottom = centerY + radius
val solidPortion = gradientSolidPortion?.coerceIn(0f, 1f)
if (solidPortion != null) {
// Draw straight middle segment with solid color
drawLine(
color = color,
start = Offset(radius, bottomY),
end = Offset(widthPx - radius, bottomY),
strokeWidth = strokeWidth,
cap = StrokeCap.Butt,
)
val gradientColorStops = listOf(
0.0f to color,
solidPortion to color,
1.0f to color.copy(alpha = 0f)
).toTypedArray()
// Draw left arc with gradient
val leftGradientBrush = Brush.linearGradient(
colorStops = gradientColorStops,
start = Offset(radius, bottomY),
end = Offset(0f, centerY),
)
val leftArcPath = Path().apply {
moveTo(0f, centerY)
arcTo(
rect = Rect(0f, leftRectTop, 2f * radius, leftRectBottom),
startAngleDegrees = 180f,
sweepAngleDegrees = if (upward) -90f else 90f,
forceMoveTo = false,
)
}
drawPath(
path = leftArcPath,
brush = leftGradientBrush,
style = Stroke(width = strokeWidth, cap = StrokeCap.Butt, join = StrokeJoin.Round),
)
// Draw right arc with gradient
val rightGradientBrush = Brush.linearGradient(
colorStops = gradientColorStops,
start = Offset(widthPx - radius, bottomY),
end = Offset(widthPx, centerY),
)
val rightArcPath = Path().apply {
moveTo(widthPx - radius, bottomY)
arcTo(
rect = Rect(widthPx - 2f * radius, leftRectTop, widthPx, leftRectBottom),
startAngleDegrees = if (upward) 90f else 270f,
sweepAngleDegrees = if (upward) -90f else 90f,
forceMoveTo = false,
)
}
drawPath(
path = rightArcPath,
brush = rightGradientBrush,
style = Stroke(width = strokeWidth, cap = StrokeCap.Butt, join = StrokeJoin.Round),
)
} else {
// Draw without gradient
val path = Path().apply {
moveTo(0f, centerY)
arcTo(
rect = Rect(0f, leftRectTop, 2f * radius, leftRectBottom),
startAngleDegrees = 180f,
sweepAngleDegrees = if (upward) -90f else 90f,
forceMoveTo = false,
)
lineTo(widthPx - radius, bottomY)
arcTo(
rect = Rect(widthPx - 2f * radius, leftRectTop, widthPx, leftRectBottom),
startAngleDegrees = if (upward) 90f else 270f,
sweepAngleDegrees = if (upward) -90f else 90f,
forceMoveTo = false,
)
}
drawPath(
path = path,
color = color,
style = Stroke(width = strokeWidth, cap = StrokeCap.Butt, join = StrokeJoin.Round),
)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment