↓ Skip to main content

Haze 2.1: Shatterproof Glass

·4 mins

Haze 2.1 is out, and Glass is now stable. Haze brings background blur and glass effects to Jetpack Compose and Compose Multiplatform. In this release, Glass surfaces can also merge and separate.

Glass that joins together
#

The drawing palette below shows stroke-width and colour controls growing out of the existing surface. The colour control separates and rejoins, then closing the palette retracts the controls again.

Open Brush to reveal the controls, then drag the colour control away and release it to rejoin.

Try the interactive sample.

The new hazeGlassContainer modifier coordinates these surfaces. Put it on a parent, then apply hazeGlass to its children as usual:

Row(Modifier.hazeGlassContainer(mergeDistance = 16.dp)) {
    repeat(3) {
        Box(Modifier.size(48.dp).hazeGlass(HazeInput.Sources(hazeState)))
    }
}

Here, hazeState connects the effects to content registered with hazeSource. Surfaces within mergeDistance of each other join into one shape, new members grow out of their neighbours, and removed ones retract. Haze animates the Glass for you.

The container guide covers grouping, transitions, and the rules for which members can merge.

Glass is stable
#

In the Haze 2.0 announcement, I said I was aiming to stabilise Glass in 2.1. That milestone is now here: haze-glass and haze-glass-material3 no longer require an ExperimentalHazeApi opt-in.

Regular and Clear are still the two built-in styles to start from. Regular diffuses the background more, while Clear keeps more detail visible behind your controls. Both support custom shapes, tint, lighting, and interaction responses.

The native Android backdrop backend and the new input-sharing feature remain experimental, with their own opt-ins.

Styles that respond to state
#

Blur and Glass styles can now read animated state directly. You can remember a style once and let its properties change with the animation:

val radius = animateDpAsState(if (expanded) 20.dp else 28.dp)
val style = remember {
    GlassStyle.regular.then {
        shape(RoundedCornerShape(radius.value))
    }
}

Modifier.hazeGlass(
    input = HazeInput.Sources(hazeState),
    style = style,
)

The animated value is read inside the style block. Haze observes that read and updates the effect on the next frame, without rebuilding the style or recomposing the hazeGlass call site.

The same approach works for HazeBlurStyle, including animated blur radii. Thanks to Mohammad Jaber for the Blur support.

Refinements beyond the demo
#

  • Glass better handles reduced-transparency preferences supplied by your app: full rendering uses stronger blur, and fallback rendering uses an opaque base with a colour you can customise.
  • Blur fixes include stale content while scrolling on Android API 31, plus corrections to masks, progressive opacity, tints, and colour filters.
  • Source-backed effects now sample the correct content across sibling Android ComposeViews.
  • Blur and Glass do less repeated rendering work and reuse more retained output.
  • In the Android sample workloads measured on a Pixel 8a, Glass used about 7% less peak GPU memory than 2.0.1. Blur Quality, where GPU memory was recorded, used at least 9% less. See the benchmark update for the comparison and its limits.
  • Experimental input sharing can reduce repeated source rendering on Android screens with several effects over the same source. In our Android nested-feed benchmark, it reduced feed replays from seven per frame to one, trading additional GPU memory and clipping to the source’s bounds plus configured outsets for less repeated rendering work. It’s off by default. See the input-sharing measurements.

Containers still have a cost. A merged surface covers its members and the space between them, and dense scrolling containers can be substantially more expensive than independent surfaces. Use them where the design calls for merging, and measure your screen. The performance guide covers the trade-offs and links to the measurements.

Try Haze 2.1
#

Use matching versions for the core and Glass artifacts:

implementation("dev.chrisbanes.haze:haze:2.1.0")
implementation("dev.chrisbanes.haze:haze-glass:2.1.0")

If you used experimental Glass in 2.0.x, a few APIs were renamed before stabilisation, so recompile code that uses it. The release notes list those changes and everything else in this release. Start with the Glass guide, then try a container around a small set of related controls.