
Creates G2-continuous rounded rectangles and capsules with configurable continuity (G1/G2), tunable arc/Bezier profiles, G1-guarantee and optimized Bézier control-point calculation for efficient rendering.
Based on the original project: Capsule
Original author: Kyant0
Curvify is a derivative work of Capsule, ported and modified under the Apache License 2.0. Modifications include the package and project renaming, absolute-direction corner variants, Bézier evaluation caching, lazy SVG bounds, KDoc coverage and the new sample app.
Curvify is a Compose Multiplatform library that creates G2 continuous rounded rectangles.
In build.gradle.kts, add
implementation("io.github.myronite:curvify:<version>")Replace the RoundedCornerShape with ContinuousRoundedRectangle or ContinuousCapsule:
// create a basic rounded corner shape
ContinuousRoundedRectangle(16.dp)
// create a capsule shape
ContinuousCapsuleContinuousRoundedRectangle resolves topStart/topEnd according to the layout direction (mirrored in RTL).
If you always want corners relative to the top-left, use the absolute variants:
// corners stay fixed regardless of layout direction
AbsoluteContinuousRoundedRectangle(topLeft = 16.dp, bottomRight = 16.dp)
AbsoluteContinuousCapsuleGrow or shrink a shape while keeping the corner curves concentric, similar to the iOS squircle insets:
val outer = ContinuousRoundedRectangle(24.dp)
val inner = outer.concentricInset(8.dp) // smaller, concentric corners
val bigger = outer.concentricOutset(4.dp) // larger, concentric cornersAlso available on the AbsoluteContinuousRoundedRectangle family.
Interpolate between two shapes (corners and continuity both animate):
val shape = lerp(startShape, stopShape, fraction)Every shape geometry is built from pure PathSegments, which can be exported:
val segments = G2Continuity().createRoundedRectanglePathSegments(
width = 200.0, height = 100.0,
topLeft = 16.0, topRight = 16.0, bottomRight = 16.0, bottomLeft = 16.0
)
val svg = segments.toSvg(asDocument = true)Custom continuity:
val g0 = G0Continuity // sharp corners
val g1 = G1Continuity // plain rounded corners, no corner smoothing
val g2 = G2Continuity(
profile = G2ContinuityProfile.RoundedRectangle.copy(
extendedFraction = 0.5,
arcFraction = 0.5,
bezierCurvatureScale = 1.1,
arcCurvatureScale = 1.1
),
capsuleProfile = G2ContinuityProfile.Capsule.copy(
extendedFraction = 0.5,
arcFraction = 0.25
)
)
// create shapes with custom continuity
ContinuousRoundedRectangle(16.dp, continuity = g2)
ContinuousCapsule(continuity = g2)The following parameters are supported by G2ContinuityProfile:
Note: It guarantees G1 continuity at least. Only if the Bezier curvature scale equals the arc curvature scale, it will have exact G2 continuity.
Drawing cubic Bézier curves on Android performs poorly. However, the Curvify library uses a very efficient method to calculate the control points, achieving optimal theoretical performance.
When the shape area is large (almost fullscreen) and the corner radius is constantly changing, performance may decrease.
Use animatedShape.copy(continuity = G1Continuity) to temporarily disable corner smoothing during the
animation.
Based on the original project: Capsule
Original author: Kyant0
Curvify is a derivative work of Capsule, ported and modified under the Apache License 2.0. Modifications include the package and project renaming, absolute-direction corner variants, Bézier evaluation caching, lazy SVG bounds, KDoc coverage and the new sample app.
Curvify is a Compose Multiplatform library that creates G2 continuous rounded rectangles.
In build.gradle.kts, add
implementation("io.github.myronite:curvify:<version>")Replace the RoundedCornerShape with ContinuousRoundedRectangle or ContinuousCapsule:
// create a basic rounded corner shape
ContinuousRoundedRectangle(16.dp)
// create a capsule shape
ContinuousCapsuleContinuousRoundedRectangle resolves topStart/topEnd according to the layout direction (mirrored in RTL).
If you always want corners relative to the top-left, use the absolute variants:
// corners stay fixed regardless of layout direction
AbsoluteContinuousRoundedRectangle(topLeft = 16.dp, bottomRight = 16.dp)
AbsoluteContinuousCapsuleGrow or shrink a shape while keeping the corner curves concentric, similar to the iOS squircle insets:
val outer = ContinuousRoundedRectangle(24.dp)
val inner = outer.concentricInset(8.dp) // smaller, concentric corners
val bigger = outer.concentricOutset(4.dp) // larger, concentric cornersAlso available on the AbsoluteContinuousRoundedRectangle family.
Interpolate between two shapes (corners and continuity both animate):
val shape = lerp(startShape, stopShape, fraction)Every shape geometry is built from pure PathSegments, which can be exported:
val segments = G2Continuity().createRoundedRectanglePathSegments(
width = 200.0, height = 100.0,
topLeft = 16.0, topRight = 16.0, bottomRight = 16.0, bottomLeft = 16.0
)
val svg = segments.toSvg(asDocument = true)Custom continuity:
val g0 = G0Continuity // sharp corners
val g1 = G1Continuity // plain rounded corners, no corner smoothing
val g2 = G2Continuity(
profile = G2ContinuityProfile.RoundedRectangle.copy(
extendedFraction = 0.5,
arcFraction = 0.5,
bezierCurvatureScale = 1.1,
arcCurvatureScale = 1.1
),
capsuleProfile = G2ContinuityProfile.Capsule.copy(
extendedFraction = 0.5,
arcFraction = 0.25
)
)
// create shapes with custom continuity
ContinuousRoundedRectangle(16.dp, continuity = g2)
ContinuousCapsule(continuity = g2)The following parameters are supported by G2ContinuityProfile:
Note: It guarantees G1 continuity at least. Only if the Bezier curvature scale equals the arc curvature scale, it will have exact G2 continuity.
Drawing cubic Bézier curves on Android performs poorly. However, the Curvify library uses a very efficient method to calculate the control points, achieving optimal theoretical performance.
When the shape area is large (almost fullscreen) and the corner radius is constantly changing, performance may decrease.
Use animatedShape.copy(continuity = G1Continuity) to temporarily disable corner smoothing during the
animation.