由于 Jetpack Compose 有 此限制 我正在寻找解决此问题的解决方案?
也许 Canvas 可以解决这个问题?如果有人能够提供如何在 Jetpack Compose 中为
Card
、Box
、Column
、Row
等渲染阴影的代码片段,利用 X 和 Y 偏移、模糊和不透明度等附加参数以及自定义实现(画布或其他东西)?
由于这个代码片段
我设法找到了解决方案fun Modifier.advancedShadow(
color: Color = Color.Black,
alpha: Float = 1f,
cornersRadius: Dp = 0.dp,
shadowBlurRadius: Dp = 0.dp,
offsetY: Dp = 0.dp,
offsetX: Dp = 0.dp
) = drawBehind {
val shadowColor = color.copy(alpha = alpha).toArgb()
val transparentColor = color.copy(alpha = 0f).toArgb()
drawIntoCanvas {
val paint = Paint()
val frameworkPaint = paint.asFrameworkPaint()
frameworkPaint.color = transparentColor
frameworkPaint.setShadowLayer(
shadowBlurRadius.toPx(),
offsetX.toPx(),
offsetY.toPx(),
shadowColor
)
it.drawRoundRect(
0f,
0f,
this.size.width,
this.size.height,
cornersRadius.toPx(),
cornersRadius.toPx(),
paint
)
}
}
根据上面的帖子,我更改了实现以匹配下面站点的参数 https://html-css-js.com/css/generator/box-shadow/
我当前的实现如下
internal fun Modifier.coloredShadow(
color: Color = Color.Black,
borderRadius: Dp = 0.dp,
blurRadius: Dp = 0.dp,
offsetY: Dp = 0.dp,
offsetX: Dp = 0.dp,
spread: Float = 0f,
modifier: Modifier = Modifier,
) = this.then(
modifier.drawBehind {
this.drawIntoCanvas {
val paint = Paint()
val frameworkPaint = paint.asFrameworkPaint()
val spreadPixel = spread.dp.toPx()
val leftPixel = (0f - spreadPixel) + offsetX.toPx()
val topPixel = (0f - spreadPixel) + offsetY.toPx()
val rightPixel = (this.size.width + spreadPixel)
val bottomPixel = (this.size.height + spreadPixel)
if (blurRadius != 0.dp) {
/*
The feature maskFilter used below to apply the blur effect only works
with hardware acceleration disabled.
*/
frameworkPaint.maskFilter =
(BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL))
}
frameworkPaint.color = color.toArgb()
it.drawRoundRect(
left = leftPixel,
top = topPixel,
right = rightPixel,
bottom = bottomPixel,
radiusX = borderRadius.toPx(),
radiusY = borderRadius.toPx(),
paint
)
}
}
)
随时添加评论并帮助完善这一要点 https://gist.github.com/hernandazevedo/dfd41b39d0156c740a195f6f5866ce20
我最近创建了阴影修改器,允许设置半径和偏移量。
@Immutable
data class Shadow(
@Stable val offsetX: Dp,
@Stable val offsetY: Dp,
@Stable val radius: Dp,
@Stable val color: Color,
)
fun Modifier.withShadow(
shadow: Shadow,
shape: Shape,
) = drawBehind {
drawIntoCanvas { canvas ->
val paint = Paint()
paint.asFrameworkPaint().apply {
this.color = Color.Transparent.toArgb()
setShadowLayer(
radius = shadow.radius.toPx(),
dx = shadow.offsetX.toPx(),
dy = shadow.offsetY.toPx(),
shadowColor = shadow.color,
)
}
val outline = shape.createOutline(size, layoutDirection, this)
canvas.drawOutline(outline, paint)
}
}
该实现基于上述实现,实现了css box-shadow规则的所有功能。
希望对大家有用
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isSpecified
data class Shadow(
val color: Color = Color.Black,
val blurRadius: Dp = 0.dp,
val spreadRadius: Dp = 0.dp,
val offset: DpOffset = DpOffset.Zero,
val inset: Boolean = false,
)
fun Modifier.boxShadow(
vararg shadowList: Shadow,
shape: Shape = RectangleShape,
clip: Boolean = true,
): Modifier {
return drawWithCache {
onDrawWithContent {
fun drawShadow(shadow: Shadow) {
val color: Color = shadow.color;
val blurRadius: Dp = shadow.blurRadius;
val spreadRadius: Dp = shadow.spreadRadius;
val offset: DpOffset = shadow.offset;
val inset: Boolean = shadow.inset;
require(color.isSpecified) { "color must be specified." }
require(blurRadius.isSpecified) { "blurRadius must be specified." }
require(blurRadius.value >= 0f) { "blurRadius can't be negative." }
require(spreadRadius.isSpecified) { "spreadRadius must be specified." }
require(offset.isSpecified) { "offset must be specified." }
drawIntoCanvas { canvas ->
val spreadRadiusPx = spreadRadius.toPx().let { spreadRadiusPx ->
when {
inset -> -spreadRadiusPx
else -> spreadRadiusPx
}
}
val hasSpreadRadius = spreadRadiusPx != 0f
val shadowOutline = shape.createOutline(size = when {
hasSpreadRadius -> size.let { (width, height) ->
(2 * spreadRadiusPx).let { outset ->
Size(
width = width + outset, height = height + outset
)
}
}
else -> size
}, layoutDirection = layoutDirection, density = this)
canvas.save()
if (inset) {
val boxOutline = when {
hasSpreadRadius -> shape.createOutline(
size = size, layoutDirection = layoutDirection, density = this
)
else -> shadowOutline
}
canvas.clipToOutline(boxOutline)
canvas.saveLayer(boxOutline.bounds, Paint().apply {
colorFilter = ColorFilter.colorMatrix(
ColorMatrix(
floatArrayOf(
1f, 0f, 0f, 0f, 0f,
0f, 1f, 0f, 0f, 0f,
0f, 0f, 1f, 0f, 0f,
0f, 0f, 0f, -1f, 255f * color.alpha
)
)
)
})
}
canvas.drawOutline(outline = shadowOutline, paint = Paint().also { paint ->
paint.asFrameworkPaint().apply {
this.color = Color.Transparent.toArgb()
setShadowLayer(
blurRadius.toPx(),
offset.x.toPx() - spreadRadiusPx,
offset.y.toPx() - spreadRadiusPx,
color.toArgb(),
)
}
})
if (inset) {
canvas.restore()
}
canvas.restore()
}
}
for (shadow in shadowList.filter { !it.inset }) {
drawShadow(shadow)
}
drawContent()
for (shadow in shadowList.filter { it.inset }) {
drawShadow(shadow)
}
}
}.let { modifier -> if (clip) modifier.clip(shape) else modifier }
}
fun Canvas.clipToOutline(
outline: Outline,
clipOp: ClipOp = ClipOp.Intersect,
) {
when (outline) {
is Outline.Generic -> clipPath(path = outline.path, clipOp = clipOp)
is Outline.Rectangle -> clipRect(rect = outline.rect, clipOp = clipOp)
is Outline.Rounded -> clipPath(
path = Path().apply { addRoundRect(outline.roundRect) }, clipOp = clipOp
)
}
}