Blur usageΒΆ
Modifier.hazeBlur takes an input, which decides what gets blurred, and a Style, which decides how
it looks.
Choosing the inputΒΆ
| Requirement | Input | What it blurs |
|---|---|---|
| Blur whatever is behind a surface | HazeInput.Backdrop(hazeState) |
Content drawn earlier in the same window, using your hazeSource content where native backdrop rendering isn't used. |
| Control exactly which sources are blurred | HazeInput.Sources(hazeState) |
Content marked with selected hazeSource modifiers. |
| Blur the modifier's own content | HazeInput.Content |
The composable's own content. |
Use Backdrop for most cases.
Source-backed BlurΒΆ
val hazeState = rememberHazeState()
val style = HazeMaterials.thin()
Box {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.hazeSource(hazeState),
) {
// Content
}
TopAppBar(
modifier = Modifier.hazeBlur(
input = HazeInput.Sources(hazeState),
style = style,
),
)
}
HazeInput.Sources also controls which sources are used and what is shown while they are
unavailable. The default, KeepLastFrame, avoids an empty flash during source transitions, but the
retained output is not guaranteed to be an exact copy of the last frame (see
Core concepts). Use ClearWhenUnavailable for privacy-sensitive content:
Modifier.hazeBlur(
input = HazeInput.Sources(
state = hazeState,
retention = HazeSourceRetention.ClearWhenUnavailable,
),
)
Own-content BlurΒΆ
Use HazeInput.Content when the modifier's own content is the input:
Image(
modifier = Modifier.hazeBlur(
input = HazeInput.Content,
style = HazeMaterials.thin(),
),
)
Android window-backdrop BlurΒΆ
On Android 37.2 or later, HazeInput.Backdrop can blur the window backdrop natively. This is
experimental and off by default. Enable it before the Blur modifiers you want to affect are
attached:
HazeFeatureFlags.isPlatformBackdropEnabled = true
Modifier.hazeBlur(
input = HazeInput.Backdrop(hazeState),
style = HazeMaterials.thin(),
)
Enabling the flag makes native rendering possible, not guaranteed. On other platforms, older
Android releases, a software canvas, or if native rendering fails, Haze uses your hazeSource
content instead; switching can take one frame. Changing the flag only affects modifiers attached
afterwards.
Native rendering blurs content drawn earlier in the same window. It can't see content drawn later,
or content in another dialog, popup, or window, and HazePerformanceMode doesn't reduce its
resolution. Use HazeInput.Sources when you need exact source selection, content from another
window, or a retention policy.
Set HazeLogger.enabled = true to log which path is chosen. A later release may enable native
rendering by default.
Enabling BlurΒΆ
Blur is enabled by default on Android 12 and newer and on all other platforms. On Android 11 and
below, Haze draws a translucent scrim instead unless you enable Blur (see
Platforms). To override the default, set blurEnabled in a
Style:
Modifier.hazeBlur(
input = HazeInput.Backdrop(hazeState),
style = HazeBlurStyle {
blurEnabled(true)
},
)
When Blur is disabled, Haze draws fallbackColorEffect instead.
Replayable StylesΒΆ
A HazeBlurStyle is a list of property writes:
val style = HazeBlurStyle {
blurEnabled(true)
blurRadius(20.dp)
noiseFactor(0.15f)
backgroundColor(Color.Black)
colorEffects(
listOf(
HazeColorEffect.tint(Color.White.copy(alpha = 0.12f)),
),
)
fallbackColorEffect(HazeColorEffect.tint(Color.Black.copy(alpha = 0.7f)))
alpha(1f)
mask(null)
progressive(null)
blurredEdgeTreatment(BlurredEdgeTreatment.Rectangle)
}
Haze applies Styles in this order:
HazeBlurDefaults.styleLocalHazeBlurStyle- The explicit
hazeBlurStyle
The last write to a property wins, both across tiers and within a Style chain:
val compact = HazeMaterials.thin().then {
blurRadius(12.dp)
noiseFactor(0f)
}
Properties a Style doesn't write keep their inherited value. The block runs each time a node resolves its Style, under snapshot observation: a change to snapshot state the block reads, such as an animated value, updates the node on the next frame without recomposition.
val radius by animateDpAsState(if (expanded) 24.dp else 0.dp)
val style = remember { HazeBlurStyle { blurRadius(radius) } }
Only snapshot state schedules an update. A plain captured value, or a MutableList you mutate, does
not. A remembered block keeps what it captured, so give remember keys or use
rememberUpdatedState for non-state inputs that change. The built-in presets compare by value, so
calling a preset again with the same inputs yields an equal Style and does not update nodes.
colorEffects copies the list you pass each time the block runs, so mutating your list afterwards
does not schedule an update. An empty list clears inherited color effects:
val noColorEffects = HazeBlurStyle {
colorEffects(emptyList())
}
Progressive Blur and masksΒΆ
Progressive Blur varies intensity across the surface:
val progressiveStyle = HazeBlurStyle {
progressive(
HazeProgressive.verticalGradient(
startIntensity = 1f,
endIntensity = 0f,
),
)
}
A mask fades the effect's opacity instead, and is usually cheaper:
val maskedStyle = HazeBlurStyle {
mask(
Brush.verticalGradient(
colors = listOf(Color.Black, Color.Transparent),
),
)
}
The two can be combined. Progressive Blur varies the intensity first, then the mask fades the complete result, so fully transparent mask regions show no effect:
val maskedProgressiveStyle = HazeBlurStyle {
progressive(HazeProgressive.horizontalGradient())
mask(
Brush.verticalGradient(
colors = listOf(Color.Black, Color.Transparent),
),
)
}
On Android 12L and below, progressive Blur is approximated (see Platforms).
Performance mode and layer expansionΒΆ
Performance mode and layer expansion are hazeBlur parameters, not Style properties:
Modifier.hazeBlur(
input = HazeInput.Sources(hazeState),
style = style,
performanceMode = HazePerformanceMode.Default,
expandLayerBounds = true,
)
See the performance guide for choosing a mode, and
Blur guidance for when to change expandLayerBounds. Measured results
are in the benchmark results.