Skip to content

Instantly share code, notes, and snippets.

@L10n42
Created June 6, 2024 17:34
Show Gist options
  • Save L10n42/d74b30af3dc0ae50358dc845363dfebc to your computer and use it in GitHub Desktop.
Save L10n42/d74b30af3dc0ae50358dc845363dfebc to your computer and use it in GitHub Desktop.
3D Border in Jetpack Compose
import android.graphics.BlurMaskFilter
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Data class representing the style of a convex effect.
*
* @property blur The blur radius for the shadow and glare effects.
* @property offset The offset distance for the shadow and glare effects.
* @property glareColor The color used for the glare effect.
* @property shadowColor The color used for the shadow effect.
*/
data class ConvexStyle(
val blur: Dp = 3.dp,
val offset: Dp = 2.dp,
val glareColor: Color = Color.White.copy(0.64f),
val shadowColor: Color = Color.Black.copy(0.64f)
)
/**
* Extension function for Modifier to draw a border with a convex effect.
*
* @param color The color of the border.
* @param shape The shape of the border.
* @param strokeWidth The width of the border stroke.
* @param convexStyle The style of the convex effect applied to the border.
*/
fun Modifier.convexBorder(
color: Color,
shape: Shape,
strokeWidth: Dp = 8.dp,
convexStyle: ConvexStyle = ConvexStyle()
) = this.drawWithContent {
val adjustedSize = Size(size.width - strokeWidth.toPx(), size.height - strokeWidth.toPx())
val outline = shape.createOutline(adjustedSize, layoutDirection, this)
drawContent()
val halfStrokeWidth = strokeWidth.toPx() / 2
translate(halfStrokeWidth, halfStrokeWidth) {
drawOutline(
outline = outline,
color = color,
style = Stroke(width = strokeWidth.toPx())
)
}
with(convexStyle) {
drawConvexBorderShadow(outline, strokeWidth, blur, -offset, -offset, shadowColor)
drawConvexBorderShadow(outline, strokeWidth, blur, offset, offset, glareColor)
}
}
private fun DrawScope.drawConvexBorderShadow(
outline: Outline,
strokeWidth: Dp,
blur: Dp,
offsetX: Dp,
offsetY: Dp,
shadowColor: Color
) = drawIntoCanvas { canvas ->
val shadowPaint = Paint().apply {
this.style = PaintingStyle.Stroke
this.color = shadowColor
this.strokeWidth = strokeWidth.toPx()
}
canvas.saveLayer(size.toRect(), shadowPaint)
val halfStrokeWidth = strokeWidth.toPx() / 2
canvas.translate(halfStrokeWidth, halfStrokeWidth)
canvas.drawOutline(outline, shadowPaint)
shadowPaint.asFrameworkPaint().apply {
xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
maskFilter = BlurMaskFilter(blur.toPx(), BlurMaskFilter.Blur.NORMAL)
}
shadowPaint.color = Color.Black
canvas.translate(offsetX.toPx(), offsetY.toPx())
canvas.drawOutline(outline, shadowPaint)
canvas.restore()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment