Jetpack Compose inner shadow

Viewed 4826

How to create an inner shadow with Jetpack Compose? The gradient should run from black on the outside to transparent on the inside. Modifier.shadow() is just for outer shadows. Using negative elevation is not working.

3 Answers

enter image description here

fun Modifier.innerShadow(
    color: Color = Color.Black,
    cornersRadius: Dp = 0.dp,
    spread: Dp = 0.dp,
    blur: Dp = 0.dp,
    offsetY: Dp = 0.dp,
    offsetX: Dp = 0.dp
) = drawWithContent {

    drawContent()

    val rect = Rect(Offset.Zero, size)
    val paint = Paint()

    drawIntoCanvas {

        paint.color = color
        paint.isAntiAlias = true
        it.saveLayer(rect, paint)
        it.drawRoundRect(
            left = rect.left,
            top = rect.top,
            right = rect.right,
            bottom = rect.bottom,
            cornersRadius.toPx(),
            cornersRadius.toPx(),
            paint
         )
         val frameworkPaint = paint.asFrameworkPaint()
         frameworkPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
         if (blur.toPx() > 0) {
             frameworkPaint.maskFilter = BlurMaskFilter(blur.toPx(), BlurMaskFilter.Blur.NORMAL)
         }
         val left = if (offsetX > 0.dp) {
             rect.left + offsetX.toPx()
         } else {
             rect.left
         }
         val top = if (offsetY > 0.dp) {
             rect.top + offsetY.toPx()
         } else {
             rect.top
         }
         val right = if (offsetX < 0.dp) {
             rect.right + offsetX.toPx()
         } else {
             rect.right
         }
         val bottom = if (offsetY < 0.dp) {
             rect.bottom + offsetY.toPx()
         } else {
             rect.bottom
         }
         paint.color = Color.Black
         it.drawRoundRect(
             left = left + spread.toPx() / 2,
             top = top + spread.toPx() / 2,
             right = right - spread.toPx() / 2,
             bottom = bottom - spread.toPx() / 2,
             cornersRadius.toPx(),
             cornersRadius.toPx(),
             paint
         )
         frameworkPaint.xfermode = null
         frameworkPaint.maskFilter = null
    }
}

Usage:

Box(
    modifier = Modifier
        .width(240.dp)
        .height(180.dp)
        .outerShadow(
            color = Color(0xff000000),
            alpha = 0.5f,
            cornersRadius = 20.dp,
            shadowBlurRadius = 30.dp,
            offsetX = 0.dp,
            offsetY = 15.dp
        )
        .clip(RoundedCornerShape(20.dp))
        .background(Color(0xFF282A2F))
        .innerShadow(
            blur = 1.dp,
            color = Color(0xff00FFFF),
            cornersRadius = 20.dp,
            offsetX = (-40.5).dp,
            offsetY = (-10.5).dp
        )
        .innerShadow(
            blur = 20.dp,
            color = Color(0xffff0000),
            cornersRadius = 20.dp,
            offsetX = 0.5.dp,
            offsetY = 0.5.dp
        )
        .padding(14.dp),
    contentAlignment = Alignment.Center
) {
    Text(
        text = stringResource(id = R.string.notification_service),
        modifier = Modifier,
        color = Color.White
     )
}

I hope it helps. outerShadow is custom modifier too, copied from other websites.

Position of Modifier.shadow relative to Modifier.background places shadow inside your component. Also you can create Modifier.drawWithContent and frameworkPaint a show with blur.

Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(20.dp)
) {
    ComponentWithInnerShadow()
    Spacer(modifier = Modifier.height(12.dp))
    ComponentWithOuterShadow()
    Spacer(modifier = Modifier.height(12.dp))
    ComponentWithCustomInnerShadow()
}

@Composable private fun ComponentWithInnerShadow() {
    Column(
        modifier = Modifier
            .clip(RoundedCornerShape(5.dp))
            .background(Color.Yellow)
            .shadow(2.dp, shape = RoundedCornerShape(5.dp))
    ) {
        Text(text = "Hello World", modifier = Modifier.padding(12.dp))
    } }

@Composable
private fun ComponentWithOuterShadow() {
    Column(
        modifier = Modifier
            .shadow(2.dp, shape = RoundedCornerShape(5.dp))
            .background(Color.Yellow)

    ) {
        Text(text = "Hello World", modifier = Modifier.padding(12.dp))
    }
}

@Composable
private  fun ComponentWithCustomInnerShadow() {
    Column(
        modifier = Modifier.innerShadow()

    ) {
        Text(text = "Hello World", modifier = Modifier.padding(12.dp))
    }
}

And with composed modifier. I didn't set parameters just set arbitrary numbers you can set your own parameters, and set color if you want to. This one looks better than standard inner shadow though. You need to provide color for foreground and shape with this one either. I set color and drew Rounded rectangle for demonstration

fun Modifier.innerShadow() = composed(
    inspectorInfo = {

    },
    factory = {

        val paint = remember() {
            Paint()
        }

        val foregroundPaint = remember() {
            Paint().apply {
                color = Color.Yellow
            }
        }

        val frameworkPaint = remember {
            paint.asFrameworkPaint()
        }

        Modifier.drawWithContent {
            this.drawIntoCanvas {
                val color = Color.LightGray

                val radius = 2.dp.toPx()

                val shadowColor = color
                    .copy(alpha = .7f)
                    .toArgb()
                val transparent = color
                    .copy(alpha = 0f)
                    .toArgb()

                frameworkPaint.color = transparent

                frameworkPaint.setShadowLayer(
                    radius,
                    0f,
                    0f,
                    shadowColor
                )
                val shadowRadius = 4.dp.toPx()

                it.drawRoundRect(
                    left = 0f,
                    top = 0f,
                    right = this.size.width,
                    bottom = this.size.height,
                    radiusX = 5.dp.toPx(),
                    radiusY = 5.dp.toPx(),
                    paint = foregroundPaint
                )

                it.drawRoundRect(
                    left = 0f,
                    top = 0f,
                    right = this.size.width,
                    bottom = this.size.height,
                    radiusX = 5.dp.toPx(),
                    radiusY = 5.dp.toPx(),
                    paint = paint
                )

                it.drawRoundRect(
                    left = shadowRadius,
                    top = shadowRadius,
                    right = this.size.width - shadowRadius,
                    bottom = this.size.height - shadowRadius,
                    radiusX = 5.dp.toPx(),
                    radiusY = 5.dp.toPx(),
                    paint = foregroundPaint
                )
                drawContent()

            }
        }
    }
)

enter image description here

This is not supported but you have alternative options:

  1. Use a border modifier with a gradient brush
  2. Use Android Canvas
  3. Use a gradient and Modifier.drawBehind to draw the inset shadow underneath the content

Source

Related