Compose setShadowLayer alternative below API 29

92 views Asked by At

So actually I have a modifier extension function to show a shadow in a composable. I'm using the code that created in this answer .

This is the code:

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

The problem is when I'm using the function in device below than API 29, the shadow doesn't showing, after some research it turns out that the setShadowLayer only supported on API level higher than 28 (android docs). So how I manage to show the shadow in below API 29?

1

There are 1 answers

0
Yurowitz On

It actually does work on APIs below 29 if you disable hardware acceleration, but we know Compose uses hardware acceleration by default and doesn't expose any direct way to modify the behavior. However, I wrap a ComposeView in an AndroidView where I force it to use Software Acceleration, as a hack to use APIs like setShadowLayer etc. Here's a random example from a piece of my code:

AndroidView(
        modifier = modifier
            .fillMaxWidth()
            .height(108.dp),
        factory = { context ->
            ComposeView(context).apply {
                setLayerType(View.LAYER_TYPE_SOFTWARE, null)
            }
        },
        update = { composeView ->
            composeView.setContent {
                Row(
                    modifier = modifier
                        .fillMaxWidth()
                        .height(108.dp)
                        .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                        .drawWithContent {
                            val blacknavy = Color(4, 16, 28)

                            drawRect(
                                color = blacknavy, alpha = 0.75f,
                                size = size,
                            )

                            drawContent()

                            if (isSelected) {
                                val bluey = Color(62, 137, 218, 255)
                                val neoSolid = Paint()
                                val neoGlow = Paint()

                                drawIntoCanvas { canvas ->
                                    neoSolid.color = bluey
                                    neoSolid.strokeWidth = 4f
                                    neoSolid.style = PaintingStyle.Stroke
                                    neoSolid.strokeJoin = StrokeJoin.Round
                                    neoSolid.strokeCap = StrokeCap.Round

                                    val neoRealGlow = neoGlow
                                        .asFrameworkPaint()
                                        .apply {
                                            set(neoSolid.asFrameworkPaint())
                                            color = Color.Transparent.toArgb()
                                            strokeWidth = 1f
                                            //maskFilter = BlurMaskFilter(15f, BlurMaskFilter.Blur.NORMAL)
                                            setShadowLayer(
                                                30f,
                                                0f,
                                                0f,
                                                Color.Red.copy(alpha = 0.9f)
                                                    .toArgb()
                                            )
                                        }


                                    canvas.drawLine(
                                        Offset(size.width, 0f),
                                        Offset(size.width, size.height),
                                        neoRealGlow.asComposePaint()
                                    )

                                    canvas.drawLine(
                                        Offset(size.width, 0f),
                                        Offset(size.width, size.height),
                                        neoSolid
                                    )
                                }
                            }
                        }
                        .clickable(
                            interactionSource = remember { MutableInteractionSource() },
                            indication = rememberRipple(),
                            onClick = onClick
                        ),
                    horizontalArrangement = Arrangement.Center,
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Icon(
                        imageVector = icon,
                        contentDescription = "",
                        tint = Color.White
                    )
                }
            }
        },
    )