How to Add Frosted Glass & Glassmorphism Effects to Your Compose UI with Haze 2

1 views 0 likes 0 comments 19 minutesOriginalTutorial

A practical 20-minute guide to integrating Haze 2 into Jetpack Compose and Compose Multiplatform projects. Learn how to implement blur effects, glassmorphism cards, and optimize performance across multiple platforms.

#JetpackCompose #ComposeMultiplatform #FrostedGlass #Glassmorphism #Kotlin #MobileDevelopment
How to Add Frosted Glass & Glassmorphism Effects to Your Compose UI with Haze 2

How to Add Frosted Glass & Glassmorphism Effects to Your Compose UI with Haze 2

Introduction: Why Do You Need a Frosted Glass Effect?

UI developers often encounter requirements like: "Add a translucent frosted glass background here to make it look premium."

Previously, achieving this in Compose meant either drawing it manually with Canvas (poor performance, jagged edges) or relying on third-party libraries (often Android-only, leaving multiplatform projects in the dark). Now, there's a library called Haze, maintained by renowned Android developer chrisbanes. It supports Android, iOS, macOS, Desktop, and even Web. Version 2.0 introduces a revamped modular API that is much clearer and easier to use.

By the end of this tutorial, you'll be able to add frosted glass blur effects and glassmorphism cards to your Compose UI in under 20 minutes. You'll also learn how to manage performance and write cross-platform compatible code.

Prerequisites

  • A project using Jetpack Compose or Compose Multiplatform
  • Kotlin 1.9+ (2.0+ recommended)
  • Compose basics: familiar with Modifier, Box, and remember
  • For multiplatform: ensure target platform modules are configured (this tutorial focuses on Android, with multiplatform differences noted)

Quick Start: Integrating Haze 2

Step 1: Understand the Modular Architecture

A common pitfall for beginners is treating Haze as a single monolithic JAR, leading to missing features or version mismatches.

Haze 2 is designed with a "bring your own features" modular approach:

Module Purpose
haze Core infrastructure: hazeSource capture + custom effect APIs
haze-blur Frosted glass blur effects
haze-blur-materials Recommended: Pre-built blur styles (thin/regular/thick)
haze-glass Glassmorphism effects (Experimental API)
haze-glass-material3 Material3-styled glass effects

Why split it? Not every screen needs glassmorphism. Importing modules on-demand reduces APK size. Also, all Haze artifacts must share the exact same version, or compilation will fail.

Step 2: Add Dependencies

In your project's build.gradle.kts (or the commonMain dependencies block for Compose Multiplatform):

kotlin 复制代码
val hazeVersion = "2.0.0-beta03"  // Verify the latest version on Maven Central

dependencies {
    // Core + Blur
    implementation("dev.chrisbanes.haze:haze:$hazeVersion")
    implementation("dev.chrisbanes.haze:haze-blur:$hazeVersion")
    
    // Pre-built blur materials (thin/regular/thick)
    implementation("dev.chrisbanes.haze:haze-blur-materials:$hazeVersion")
    
    // If you need glassmorphism, add:
    // implementation("dev.chrisbanes.haze:haze-glass:$hazeVersion")
}

The library is currently in beta. If you're upgrading from Haze 1.x, refer to the official migration guide. The core change is the shift from chained API calls to a new paradigm using hazeBlur/hazeGlass with HazeInput and Style.

Tutorial 1: Overlapping a Frosted Glass Layer on an Image

This is the most common use case: a large background image with a semi-transparent blurred layer on top for a title or card.

kotlin 复制代码
@Composable
fun FrostedGlassImageDemo(painter: Painter) {
    // 1. Create a Haze state object to pass captured content between the source and effect layers
    val hazeState = rememberHazeState()

    Box {
        // 2. Source layer: Marked as the haze source
        Image(
            painter = painter,
            contentDescription = null,
            modifier = Modifier
                .fillMaxSize()
                .hazeSource(hazeState),  // Tells Haze: capture this layer
        )

        // 3. Effect layer: Applies blur over the source
        Box(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .fillMaxWidth()
                .height(200.dp)
                .hazeBlur(
                    input = HazeInput.Sources(hazeState),
                    style = HazeMaterials.thick(),  // Use pre-built thick blur style
                )
                .padding(16.dp)
        ) {
            Text(
                text = "Bottom Title",
                color = Color.White,
                style = MaterialTheme.typography.headlineSmall,
            )
        }
    }
}

What's happening here?

  1. rememberHazeState(): A state container. Haze uses it internally to capture the source layer's pixels and pass them to the effect layer. remember ensures it isn't recreated on recomposition.
  2. Modifier.hazeSource(hazeState): Marks the source content. Haze captures this layer's pixels using a GraphicsLayer under the hood.
  3. Modifier.hazeBlur(input = HazeInput.Sources(hazeState), style = ...): Applies the blur to the effect layer. HazeInput.Sources indicates that the blur data comes from the previously marked hazeSource.

Key Concept: Haze 2 decouples the "source" and the "effect". The source can be any Composable (Image, LazyColumn, or even an entire screen), and the effect layer overlays independently without interference.

What if I want to blur the effect layer's own content?

Replace HazeInput.Sources(hazeState) with HazeInput.Content. The effect layer will then blur its own child components, which is useful for floating panels.

Tutorial 2: Glassmorphism Card

The Glass effect goes a step further than Blur—it adds refraction, highlights, rounded corners, and other skeuomorphic details. Note that this module is marked @ExperimentalHazeApi.

kotlin 复制代码
@OptIn(ExperimentalHazeApi::class)
@Composable
fun GlassCardDemo(painter: Painter) {
    val hazeState = rememberHazeState()

    Box {
        // Background image as source
        Image(
            painter = painterResource(id = R.drawable.background),
            contentDescription = null,
            modifier = Modifier
                .fillMaxSize()
                .hazeSource(hazeState),
        )

        // Glass card
        Box(
            modifier = Modifier
                .align(Alignment.Center)
                .size(280.dp, 160.dp)
                .hazeGlass(
                    input = HazeInput.Sources(hazeState),
                    style = GlassStyle.regular.then {
                        tint(Color.White.copy(alpha = 0.16f))
                        shape(RoundedCornerShape(20.dp))
                    },
                )
                .padding(24.dp)
        ) {
            Column {
                Text("Glassmorphism Card", color = Color.White, fontSize = 20.sp)
                Spacer(Modifier.height(8.dp))
                Text("Frosted glass with refraction and highlights", color = Color.White.copy(0.8f))
            }
        }
    }
}

GlassStyle.regular is a great starting point, offering default refraction blur, highlights, and shadows. The clear variant provides more transparency, ideal when you need to see the background clearly.

Performance Tuning: Avoid UI Lag

Haze's default performance mode is HazePerformanceMode.Default, which automatically adjusts based on the platform and device. In most cases, you won't need to tweak it, but if you experience frame drops, follow this troubleshooting order:

  1. Test outside of debug mode: Debug builds disable many GPU optimizations. Use a release or benchmark build on a physical device.
  2. Switch performance modes: BalancedPerformanceFixed(...) (fixed blur radius for maximum performance).
  3. Reduce source layer area: The larger the hazeSource capture area, the higher the overhead. Mark only the necessary content regions.
  4. CameraX note: If applying blur over a live camera preview, you must use PreviewView.ImplementationMode.COMPATIBLE, as SurfaceView cannot be captured.

Common Pitfalls

  • Version mismatch: All dev.chrisbanes.haze artifacts must use the exact same version. Otherwise, runtime behavior may be unpredictable.
  • Beta API changes: Haze 2 is still in beta. Watch for breaking changes during major upgrades and consult official migration docs.
  • Desktop & Web rendering differences: Blur implementations rely on underlying platform rendering engines. Visual differences may occur in edge cases. Always verify on each target platform.

Summary

We've covered three key areas:

  1. Understanding Haze 2's "source-effect" decoupled architecture and modular dependencies.
  2. Implementing a frosted glass title bar in ~20 lines of code.
  3. Creating a glassmorphism card with hazeGlass and optimizing performance.

Next Steps: Check out the official Sample Repository for more scenarios like LazyList blur and navigation bar blur. If you're working on Compose Multiplatform for Desktop or Web, prioritize testing on your target platforms.

Frosted glass isn't magic, but using the right tools can save you hours of manual Canvas drawing and Shader tweaking. May your UI reviews be approved on the first try.

Last Updated:2026-09-24 10:10:30

Comments (0)

Post Comment

Loading...
0/500
Loading comments...