Jetpack Compose 中的 X 和 Y 偏移、模糊、不透明度和阴影颜色?

问题描述 投票:0回答:4

由于 Jetpack Compose 有 此限制 我正在寻找解决此问题的解决方案?

也许 Canvas 可以解决这个问题?如果有人能够提供如何在 Jetpack Compose 中为

Card
Box
Column
Row
等渲染阴影的代码片段,利用 X 和 Y 偏移、模糊和不透明度等附加参数以及自定义实现(画布或其他东西)?

android canvas android-jetpack shadow android-jetpack-compose
4个回答
19
投票

由于这个代码片段

我设法找到了解决方案
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
        )
    }
}

1
投票

根据上面的帖子,我更改了实现以匹配下面站点的参数 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


1
投票

我最近创建了阴影修改器,允许设置半径和偏移量。

@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)
    }
}

0
投票

该实现基于上述实现,实现了css box-shadow规则的所有功能。

  1. 能够在同一个盒子上应用多个阴影
  2. 能够设置自定义形状(例如:RoundedCornerShape)
  3. 可调节(偏移、模糊、扩散、颜色)
  4. 支持内阴影

希望对大家有用

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
        )
    }
}

© www.soinside.com 2019 - 2024. All rights reserved.