Glassmorphism/Blur effect in part of the image with Jetpack Compose?

1.7k Views Asked by At

I need to make a card (or other type of layout) with the design you can see below.

enter image description here

enter image description here

The problem: I don't know how to apply this kind of glassmorphism/blur effect to the bottom of the image where the text is.

I leave you the code that I have done below to make it easier. For the tests I'm using an image taken from Google, I think it has no influence.

@Preview
@Composable
fun BoxCard() {
    Card(elevation = 0.dp, shape = RectangleShape) {
        Box(contentAlignment = Alignment.BottomCenter) {
            Image(
                painter = painterResource(id = R.drawable.scale),
                contentDescription = null,
                contentScale = ContentScale.Fit
            )

            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(45.dp)
                    .background(Color.White)
            ) {
                Column(modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)) {
                    Text(text = "Title Title")
                    Text(text = "Subtitle Subtitle")
                }
            }
        }
    }
}
2

There are 2 best solutions below

0
puszkinowski On

there is a blur modifier, but works only with Android 12 and above.

Modifier.blur(radius = 4.dp)
0
Dark White On

Code result: Github

Backward compatible blur effect

To achieve this blur effect:

  • layer1: Background image
  • layer2: Blur the background image and clip it

PS: you will need to add filter

Tip: How to get the Glass-morphism effect from a designer perspective: https://www.youtube.com/watch?v=PFADyVTX97w

    Box(modifier = Modifier) {
    // Background image
    Image(painter = painterResource(id = R.drawable.a), contentDescription = null)
    
    val source = ImageBitmap.imageResource(id = R.drawable.a).asAndroidBitmap()
    
    // Blur effect, Backward compatibility
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
      val blurredBitmap = legacyBlurImage(
        source = source,
        blurRadio = 25f,
//        blurLayer = 5 // Todo: if more blur needed, uncomment and tweak it
      )
      BlurImage(
        bitmap = blurredBitmap,
        modifier = Modifier
      )
    } else {
      BlurImage(
        bitmap = source,
        modifier = Modifier.blur(radius = 10.dp, edgeTreatment = BlurredEdgeTreatment.Rectangle)
      )
    }
  }
@Suppress("DEPRECATION")
@Composable
private fun legacyBlurImage(
  source: Bitmap,
  blurRadio: Float = 25f,
  blurLayer: Int = 1,
): Bitmap {
  val bitmap = source.copy(Bitmap.Config.ARGB_8888, true)
  val renderScript = RenderScript.create(LocalContext.current)
  for (i in 0 until blurLayer) {
    val bitmapAlloc = Allocation.createFromBitmap(renderScript, bitmap)
    ScriptIntrinsicBlur.create(renderScript, bitmapAlloc.element).apply {
      setRadius(blurRadio)
      setInput(bitmapAlloc)
      forEach(bitmapAlloc)
    }
    bitmapAlloc.copyTo(bitmap)
  }
  renderScript.destroy()
  return bitmap
}

Shape and filters omitted:

@Composable
private fun BlurImage(
  modifier: Modifier = Modifier,
  bitmap: Bitmap,
) {
  Image(
    bitmap = bitmap.asImageBitmap(),
    contentDescription = null,
    modifier = modifier
      .padding(16.dp)
      .clip(CurvedBorder(cornerRadius = 24.dp))
      .drawWithCache {
        onDrawWithContent {
          // ... maybe shadow filter
          // Content
          this.drawContent()
          // ... light filter on top of the blur
        }
      }
  )
}