Skip to content

Instantly share code, notes, and snippets.

@trenskow
Created May 13, 2025 02:29
Show Gist options
  • Save trenskow/adccbca95296ae3979b31ed1c58d4bea to your computer and use it in GitHub Desktop.
Save trenskow/adccbca95296ae3979b31ed1c58d4bea to your computer and use it in GitHub Desktop.

Shadows

A Compose implementation of shadows (drop and inner) as used in Figma.

import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Paint
fun Canvas.withSavedState(block: Canvas.() -> Unit) {
save()
try {
block()
} finally {
restore()
}
}
fun Canvas.withSavedLayer(
bounds: Rect,
paint: Paint,
block: Canvas.() -> Unit
) {
saveLayer(
bounds = bounds,
paint = paint)
try {
block()
} finally {
restore()
}
}
import android.graphics.BlurMaskFilter
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
fun Modifier.dropShadow(
shape: Shape,
color: Color,
blur: Dp = 0.dp,
offsetX: Dp = 0.dp,
offsetY: Dp = 0.dp,
showBehindTransparentAreas: Boolean = false,
blendMode: BlendMode = BlendMode.SrcOver
) = this.drawBehind {
drawIntoCanvas { canvas ->
with(canvas) {
val shadowOutline = shape.createOutline(
size = size,
layoutDirection = layoutDirection,
density = this@drawBehind)
val paint = Paint()
paint.color = color
paint.blendMode = blendMode
if (blur.toPx() > 0) {
paint.asFrameworkPaint().apply {
maskFilter = BlurMaskFilter(
blur.toPx(),
BlurMaskFilter.Blur.NORMAL)
}
}
withSavedLayer(
bounds = Rect(
offset = Offset(
x = offsetX.toPx() - blur.toPx() * 2,
y = offsetY.toPx() - blur.toPx() * 2),
size = size.copy(
width = size.width + offsetX.toPx() + (blur.toPx() * 4),
height = size.height + offsetY.toPx() + (blur.toPx() * 4))),
paint = paint
) {
withSavedState {
translate(
dx = offsetX.toPx(),
dy = offsetY.toPx())
drawOutline(shadowOutline, paint)
}
if (!showBehindTransparentAreas) {
val cutoutPaint = Paint()
cutoutPaint.color = Color.Black
cutoutPaint.blendMode = BlendMode.Clear
drawOutline(
shadowOutline,
cutoutPaint)
}
}
}
}
}
@Preview
@Composable
fun RoundedRectangleShadowedPreview() {
Box(
modifier = Modifier
.width(200.dp)
.height(200.dp)
.background(Color.Red),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.width(100.dp)
.height(100.dp)
.dropShadow(
shape = RoundedCornerShape(16.dp),
color = Color.Black,
blur = 10.dp,
offsetX = 10.dp,
offsetY = 10.dp,
showBehindTransparentAreas = false
)
.background(
shape = RoundedCornerShape(16.dp),
color = Color.White.copy(alpha = 0.5f),
),
contentAlignment = Alignment.Center
) {
Text("Hello")
}
}
}
import android.graphics.BlurMaskFilter
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathOperation
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.addOutline
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
fun Modifier.innerShadow(
shape: Shape,
color: Color = Color.Black,
blur: Dp = 4.dp,
offsetX: Dp = 2.dp,
offsetY: Dp = 2.dp,
blendMode: BlendMode = BlendMode.SrcOver
) = this
.drawWithContent {
drawContent()
drawIntoCanvas { canvas ->
with(canvas) {
val shadowOutline = shape.createOutline(
size = size,
layoutDirection = layoutDirection,
density = this@drawWithContent
)
val paint = Paint()
paint.color = color
paint.blendMode = blendMode
withSavedLayer(
bounds = size.toRect(),
paint = paint
) {
drawOutline(
outline = shadowOutline,
paint = paint)
paint.asFrameworkPaint().apply {
xfermode = PorterDuffXfermode(
PorterDuff.Mode.CLEAR)
if (blur.toPx() > 0) {
maskFilter = BlurMaskFilter(
blur.toPx(),
BlurMaskFilter.Blur.NORMAL)
}
}
withSavedState {
paint.color = Color.Black
translate(
dx = offsetX.toPx(),
dy = offsetY.toPx())
drawOutline(
outline = shadowOutline,
paint = paint)
}
drawPath(
Path().apply {
op(
path1 = Path().apply {
addRect(
rect = this@drawWithContent.size.toRect(),
)
},
path2 = Path().apply {
addOutline(
outline = shadowOutline)
},
PathOperation.Difference
)
},
paint = Paint().apply {
this.color = Color.Black
this.style = PaintingStyle.Fill
this.blendMode = BlendMode.Clear
}
)
}
}
}
}
@Composable
@Preview
fun InnerShadowModifierPreview() {
Box(
modifier = Modifier
.background(
color = Color.White)
.padding(16.dp)
) {
Text(
text = "Hello World",
color = Color.Black.copy(alpha = 0.5f),
modifier = Modifier
.graphicsLayer {
compositingStrategy = CompositingStrategy.Offscreen
}
.background(Color.Red, RoundedCornerShape(10.dp))
.innerShadow(
shape = RoundedCornerShape(9.dp),
color = Color.White,
blur = 4.dp,
offsetX = 5.dp,
offsetY = 5.dp
)
.innerShadow(
shape = RoundedCornerShape(9.dp),
color = Color.Black,
blur = 4.dp,
offsetX = (-2).dp,
offsetY = (-2).dp
)
.padding(16.dp)
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment