Blur usage¶
Blur works with a current-window Backdrop input, exact captured Sources input, or the modifier's
own Content input. All modes use the typed hazeBlur modifier and the same replayable Style.
Choosing the input¶
| Requirement | Input | What it consumes |
|---|---|---|
| Normal built-in Blur behind content | HazeInput.Backdrop(hazeState) |
All earlier pixels in the same window, with source capture as fallback. |
| Exact source ownership or selection | HazeInput.Sources(hazeState) |
Pixels captured by selected hazeSource modifiers. |
| Blur the modifier's own content | HazeInput.Content |
Content drawn by the modifier's composable. |
Use Backdrop for the usual built-in Blur case. Its fallback source input applies only if
source capture is needed; it does not filter the native window backdrop.
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 owns source selection and retained-output behavior. The default
KeepLastFrame policy avoids an empty flash during source transitions. 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¶
HazeInput.Backdrop is portable, while its native path is an experimental eligibility path. Set
the process-wide flag before attaching the effect nodes that should observe it:
HazeFeatureFlags.isPlatformBackdropEnabled = true
Modifier.hazeBlur(
input = HazeInput.Backdrop(hazeState),
style = HazeMaterials.thin(),
)
The current default is false. true makes native rendering eligible, not guaranteed: built-in
effect support, the full Android 37.2 gate, the window and canvas, native setup, and the draw must
all succeed. This is same-window, previous-pixel ordering—not selected-source capture. It cannot
see later draw operations or pixels from another dialog, popup, or window. Native sampling stays
at compositor resolution; HazePerformanceMode does not downsample that input. On older Android
releases, other platforms, a software canvas, or after native setup/draw failure, the modifier uses
the configured source fallback. That decision is sticky until detachment and the transition may
take one frame. Changing the flag affects later attachments only, and healthy native consumers do
not record fallback source layers.
Set HazeLogger.enabled = true for selection and fallback messages. The native draw is marked by
the HazeBackdrop.draw trace section. These diagnostics do not establish a performance result;
physical Android 37.2 acceptance is still pending. A later release may flip the default, retain a
temporary false escape hatch, and then remove the experimental flag.
Use HazeInput.Sources when its exact selection, cross-window, or retention semantics must govern
the actual input. See
ADR-0010 for the complete boundary.
Enabling Blur¶
Blur is enabled by default only where Haze considers the platform implementation reliable. To
override that decision, write blurEnabled in a Style:
Modifier.hazeBlur(
input = HazeInput.Backdrop(hazeState),
style = HazeBlurStyle {
blurEnabled(true)
},
)
When Blur is disabled, Haze draws the configured fallback scrim instead.
Replayable Styles¶
HazeBlurStyle is an opaque program of Blur-specific 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)
}
Style resolution always replays these tiers in 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)
}
If a replacement Style omits blurRadius, the local or default value becomes visible again. Styles
are immutable and safe to share; create a replacement Style when the appearance needs to change.
Caller-owned color-effect lists are snapshotted when the Style is created. An explicit 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 and is usually cheaper:
val maskedStyle = HazeBlurStyle {
mask(
Brush.verticalGradient(
colors = listOf(Color.Black, Color.Transparent),
),
)
}
Performance mode and layer expansion¶
Performance mode and layer expansion are structural modifier policies, not Style properties:
Modifier.hazeBlur(
input = HazeInput.Sources(hazeState),
style = style,
performanceMode = HazePerformanceMode.Adaptive,
expandLayerBounds = true,
)
See the performance guide for mode selection and Blur guidance for the cost and edge behaviour of layer expansion.
Controlled calibration reference¶
The latest controlled Blur measurements are in the performance-mode calibration.