haze
Progressive blur for Flutter: a Gaussian over the backdrop, strongest at one edge and fading continuously to nothing, with an optional tint on the same falloff. iOS 26's scroll edge effect as a plain widget.
Stack(children: [
Image.network(url, fit: BoxFit.cover),
Positioned(
left: 0, right: 0, bottom: 0, height: 160,
child: Haze(
edge: HazeEdge.bottom,
sigma: 12,
tint: CupertinoColors.black.withValues(alpha: .5),
child: Padding(
padding: EdgeInsets.all(20),
child: Text('Light on the dunes'),
),
),
),
]);
Parameters
edge |
HazeEdge.top / bottom / left / right: where the blur is strongest. |
sigma |
Peak blur at edge, logical px. |
tint |
Wash colour; its alpha is the peak opacity at edge. null = blur only. |
enabled |
Paint the child only, layout unchanged. |
child |
Drawn above the effect. Sizes the widget when given. |
The height is whatever the parent gives it. Everything else is worked out.
How it works
Built like Apple's effect (ScrollEdgeEffectView feeding Core Animation's
variableBlur a mask) and like Glur: one
peak sigma, and a per-pixel fraction of it that reaches zero inside the
rectangle. A bundled fragment shader runs it in two separable passes
(ui.ImageFilter.shader).
- Hold, then fade. Blur holds at full strength over the first 41% of the span and the tint over 35%, then both fall on a smootherstep. Both values are measured off the system effect.
- Geometric sigma ramp. Apparent blur tracks the ratio between sigmas
(1 → 3px is huge, 15 → 30px barely visible), so each step along the fade
multiplies sigma by the same factor, from 1 physical px up to
sigma. Every stretch adds the same perceived blur, with no jump to full strength. - No lines. The kernel is tapered to exactly zero at its reach, so a tap entering it as sigma changes row to row adds nothing abruptly. The output is dithered below one 8-bit code.
- Fits its rectangle. If the fade is too short to carry
sigmaout smoothly (it must be wider than the blur's own reach),sigmais lowered.Haze.fitSigma(span, sigma)returns the value painted. - Premultiplied-correct tint, so it also covers transparent overlay areas, such as the region over a platform view.
- Bounds are recomputed at paint time. During a route transition the blur is off and eases back in when the page lands.
Without Impeller shader filters, or before the shader loads, it falls back to
contiguous bands of plain BackdropFilter blur on the same curve. Those
do show their steps. Changing the shader needs a full restart; hot reload
does not rebuild it.
Platform views
A shader filter cannot reach a native view: the iOS embedder only forwards
uniform ImageFilter.blur to platform views. Native content under the effect
has to be brought into Flutter first, for example as a snapshot drawn under
it.
Example
cd example && flutter run
Libraries
- haze
- Progressive (gradient) blur with an optional tint, as one widget.