The view
The capture pipeline the Skia and web backends need, and its controllers.
LiquidGlassView
classLiquidGlassViewController
classLiquidGlassController
classLiquidGlassShaders
classLiquidGlassEngine
enumLiquidGlassRefreshRate
classLiquidGlassPosition
classLiquidGlassOffsetPosition
classLiquidGlassAlignPosition
The capture pipeline. Wraps your page, rasterizes it once per frame into a texture, and hands that texture to every lens inside it as the thing to refract. Required on Skia and on the web, where a shader cannot read the backdrop by itself; harmless on Impeller, which can.
Constructors
const LiquidGlassView({super.key, this.controller, required this.backgroundWidget, this.child, this.pixelRatio = 1.0, this.realTimeCapture = true, this.useSync = true, this.refreshRate = LiquidGlassRefreshRate.deviceRefreshRate, this.useImpellerBackdrop, this.regionCapture = false, this.batch = true, this.foregroundBatchId, this.adaptiveSampling})| Parameter | Type | Default | Description |
|---|---|---|---|
key | Key? | — | |
controller | LiquidGlassViewController? | — | Controls the liquid-glass rendering performance and synchronization pipeline. Manages how often background captures and shader updates occur to balance visual quality and frame rate performance. |
backgroundWidgetrequired | Widget | — | The widget tree drawn behind every lens in this view. Typically a static or animated background (such as an Image, Stack, or complex layout) over which the lenses apply refraction and effects. Required: a LiquidGlassView exists to provide the captured background its lenses refract. On Skia / Web this is the capture source. On Impeller it renders normally behind the content and the live backdrop sampling picks it up. If you don't need a refractable background (Impeller only), don't use a LiquidGlassView at all — place LiquidGlassLens widgets directly in your tree; they work standalone there. |
child | Widget? | — | An arbitrary widget tree rendered on top of backgroundWidget and below the children lenses. Place LiquidGlassLens widgets anywhere inside it — in a Stack, a Column, a scrollable — and they connect to this view automatically: * On Impeller they refract the live backdrop behind them. * On Skia / Web they refract this view's captured backgroundWidget (which is required for refraction there). |
pixelRatio | double | 1.0 | The device pixel ratio used when capturing and rendering the lens effects. Higher values enhance lens content quality and clarity but also significantly impact performance by increasing GPU memory usage and rendering cost. If the background widget covers the entire screen, this setting can cause a high performance impact. In such cases, it is recommended to keep the value below 1.0 and rely on blur effects for smoother visuals instead of higher pixel density. A value of 0.0 uses the device’s default pixel ratio, while 1.0 is the maximum recommended value for maintaining a balance between visual quality and frame rate. |
realTimeCapture | bool | true | Enables or disables real-time background capture for the lenses. When true, the background beneath each lens is updated every frame, producing dynamic refraction. When false, a cached snapshot is reused for better efficiency. |
useSync | bool | true | Determines whether lens rendering is synchronized with Flutter’s frame callbacks. When true, updates are aligned with Flutter’s rendering pipeline, resulting in smoother animations and generally faster performance. When false, updates run asynchronously, which can provide higher throughput on powerful devices, but may introduce slight delays or less consistent frame timing. It is slower than synchronous mode, but it becomes very stable when the pixel ratio is low (e.g., around 0.5). |
refreshRate | LiquidGlassRefreshRate | LiquidGlassRefreshRate.deviceRefreshRate | Controls how frequently the background is re-captured while real-time updates are enabled. - LiquidGlassRefreshRate.low = ~10 FPS (energy saving) - LiquidGlassRefreshRate.medium = ~24 FPS (balanced) - LiquidGlassRefreshRate.high = ~60 FPS (smooth) - LiquidGlassRefreshRate.deviceRefreshRate = tries to match the display refresh rate |
useImpellerBackdrop | bool? | — | Override for the Impeller fast-path detection. When non-null, forces the renderer to use the BackdropFilter(filter: ImageFilter.shader(...)) path (true) or the legacy RepaintBoundary + toImage capture path (false). When null (the default), the renderer auto-detects via ui.ImageFilter.isShaderFilterSupported. Note that this getter can return true on Skia in newer Flutter versions even when Impeller is disabled — if you launch with --no-enable-impeller and lenses fail to render, set this to false explicitly. |
regionCapture | bool | false | Per-lens region capture (Skia sync path only). When true, each per-frame capture grabs one small sub-image per lens (the lens's own rect plus a small safety margin) instead of rasterizing the whole backgroundWidget, and the shader remaps each sub-image via u_imageOffset/u_imageSize. Refraction always samples inward, so the lens rect is all the shader needs. This is a performance optimization for views whose lenses cover a small fraction of a large background (e.g. one draggable lens over a full-screen photo): the capture cost scales with the captured area. With many lenses spread across the view the per-capture overhead multiplies, so measure before enabling. No effect on the Impeller path (which samples the live backdrop directly, with no captures at all) or on views with useSync: false (async captures are always full-frame). |
batch | bool | true | Whether the view batches its lenses — see LiquidGlassBatch. true (the default) puts every lens in child on one shared read of the backdrop, and every lens in backgroundWidget on another. Two batches, not one: what floats in child is meant to refract what scrolls under it in the background, and a member of a batch cannot see another member's glass. Only the Impeller path has a read to share; on the capture path this changes nothing. The batch's one rule applies inside each: members must not overlap. Two lenses in child that do overlap belong in a LiquidGlassBatch.exclude, which gives them reads of their own. false inserts no batch at all, so a LiquidGlassBatch the app put around the view still reaches the lenses inside it. |
foregroundBatchId | int? | — | The key the batch over child carries, when a host owns it. null (the default) gives that batch a key of its own. A host that runs several views but wants one read across everything that floats in them hands the same key to each — LiquidGlassScaffold does, so its tab bar, its dialog and the sheets presented over it share one. The background's batch is never shared this way: what floats refracts it. Ignored with batch off. Library-internal. |
adaptiveSampling | LiquidGlassAdaptiveSampling? | — | Enables the background-luminance sampler behind LiquidGlassAdaptivity (see LiquidGlassStyle.adaptivity): a tiny second capture of backgroundWidget at a low pixel ratio and frame limit. null (the default) means no sampling for this view — adaptive descendants fall back to their manual permanentBrightness / platform brightness. Pass const LiquidGlassAdaptiveSampling() to opt in with the standard tuning (pixelRatio 0.05, frameLimit 8). Even when set, the sampler runs only while at least one descendant has adaptivity enabled — otherwise it costs nothing, not even a timer. And while it runs, captures are idle-gated: a static screen (no rendered frames) triggers no captures at all. |
Declared in lib/src/widgets/liquid_glass_view.dart
A handle on one LiquidGlassView: it forces a fresh capture of the background and reports when one is ready, for the frames where the view cannot know by itself that what is behind the glass has changed.
Methods
| Signature | Description |
|---|---|
Future<void> captureOnce() | Captures a single static frame of the background widget of the LiquidGlassView. This performs a one-time capture and updates every lens attached to the view using that snapshot. No continuous updates are performed. Use this when: - you want a snapshot-based lens (static blur/distortion), - performance is more important than real-time capturing, - the background does not need continuous updates. If real-time capture is active, this does not stop it. |
void startRealtimeCapture() | Starts real-time capturing of the background widget of the LiquidGlassView. This continuously updates the background at a rate determined by the internal refresh pipeline. It enables fully dynamic liquid-glass effects (blurring, distortion, magnification, etc.) that react to movement or animations behind the lens. Use this when: - you want the lens background to change with the scene, - you want a live "glass" effect instead of a static snapshot. The update rate depends on your internal configuration (e.g., refresh rate, device pixel ratio, async/sync mode). |
void stopRealtimeCapture() | Stop real-time capturing of the background widget of the LiquidGlassView. After calling this, the LiquidGlassView stops updating the background content and uses static background using the last captured frame. Use this when: - the lens no longer needs the background to be updated, - improving performance or battery usage, - switching screens or reducing load. Safe to call even if real-time capture is not active. |
Declared in lib/src/controllers/liquid_glass_view_controller.dart
LiquidGlassController#
A handle on a single lens: it moves one, resizes it, and restyles it from outside the widget tree that built it.
Methods
| Signature | Description |
|---|---|
void showLiquidGlass( {int? animationTimeMillisecond, VoidCallback? onCompl… | Shows the liquid-glass lens with an animation. This method animates the distortion from its starting value (distortionBegin) up to 1.0, making the lens fully visible. Parameters: - animationTimeMillisecond: Optional override for the animation duration. If not provided, the default duration inside the widget is used. - onComplete: Optional callback executed after the animation finishes. Use this when you want to reveal the lens with a smooth transition. |
void hideLiquidGlass( {int? animationTimeMillisecond, VoidCallback? onCompl… | Hides the liquid-glass lens with an animation. This method animates the distortion from 1.0 back down to distortionBegin, making the lens appear to fade out or soften. Parameters: - animationTimeMillisecond: Optional override for how long the hide animation should take. - onComplete: Optional callback triggered after the hide animation ends. Use this when you want to dismiss the lens smoothly. |
void resetLiquidGlassPosition() | Instantly resets the liquid-glass lens position. This method does not animate. It immediately snaps the lens back to its default/original position. Useful for: - resetting drag gestures, - centering the lens, - restoring position after navigation changes. |
Declared in lib/src/controllers/liquid_glass_controller.dart
LiquidGlassShaders#
App-wide cache for the compiled liquid-glass fragment programs.
A FragmentProgram is expensive to compile and identical for every lens in the app, so it is loaded once and shared. Individual FragmentShader instances (which hold per-lens uniform state) are created from the cached programs and owned by their lens.
Per-backend programs
The shape-gradient method differs by backend: Impeller uses hardware derivatives (dFdx), which are invalid SkSL — so Skia/web loads a separate entry that selects the analytic gradient instead. The programs are therefore cached per backend (impeller true/false), and every call passes the backend it needs:
impeller == true→liquid_glass.frag/liquid_glass_border.fragimpeller == false→liquid_glass_skia.frag/..._border_skia.frag
LiquidGlassView and the standalone LiquidGlassLens both load through this cache, so whichever mounts first pays the one-time async compile and every later mount on the same backend gets its shaders synchronously.
Call ensureLoaded ahead of time (e.g. in main() before runApp) to guarantee even the very first lens — or the first LiquidGlassBlender — renders on its first frame. It loads every program the package draws with: the lens's main and border programs and the blender's merged-surface program. With no argument it preloads the engine's native backend (ui.ImageFilter.isShaderFilterSupported):
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await LiquidGlassShaders.ensureLoaded();
runApp(const MyApp());
}Static members
| Signature | Description |
|---|---|
bool isLoadedFor(bool impeller) | Whether the lens's two programs for impeller are compiled and shader instances can be created synchronously via createMainShader/createBorderShader. |
bool get isLoaded | Whether the engine's native backend is loaded. Convenience for callers that don't track the backend explicitly. |
Future<void> ensureLoaded([bool? impeller]) | Loads and compiles every fragment program the package draws with, for impeller (defaults to the engine's native backend): the lens's main and border programs, and the merged-surface program LiquidGlassBlender and LiquidGlassMorph draw with. One await in main() and the first lens and the first blender are both glass on their first frame. Safe to call repeatedly and from multiple call sites — concurrent callers for the same backend share the in-flight futures, and once loaded it completes synchronously. |
ui.FragmentShader createMainShader([bool? impeller]) | Creates a fresh main-shader instance for impeller (defaults to the engine's native backend). isLoadedFor must be true for that backend. |
ui.FragmentShader createBorderShader([bool? impeller]) | Creates a fresh border-shader instance for impeller (defaults to the engine's native backend). isLoadedFor must be true for that backend. |
Declared in lib/src/widgets/lens/liquid_glass_shaders.dart
Package-wide renderer switches.
Static members
| Signature | Description |
|---|---|
bool liteGlassOnSkia | Lite glass for every lens on Skia / Web, instead of the capture path. Off (the default), a lens inside a LiquidGlassView on Skia refracts the view's captured background. On, the view takes no glass capture at all and every lens draws LiquidGlassLite — frost, tint and a rim cut from the backdrop, no refraction — the same thing a lens outside any view draws. The adaptive sampler is not a glass capture and keeps running. Set it once, before the first lens builds: A single lens can opt in on its own through LiquidGlassStyle.liteGlass. |
bool liteGlassOnImpeller | Lite glass for every lens on Impeller, instead of the shader. Off (the default), a lens on Impeller refracts the live backdrop through its fragment shader. On, every lens draws LiquidGlassLite instead: no shader pass, no slot in the lens budget. Same rule as liteGlassOnSkia, on the other engine. |
bool get liteGlass | Whether every lens is drawing lite glass right now: the switch for the engine this app runs on. |
LiquidGlassLitePickup litePickup | Where a lens drawn lite by the switches above — or while its shaders load — takes its rim colour from. backdrop (the default) reads the background along the rim as the shader does, for one read; blend and surface cost no read; none is white light. A style that sets its own LiquidGlassStyle.liteGlass names its own and ignores this. |
Declared in lib/src/widgets/liquid_glass_engine.dart
LiquidGlassRefreshRate#
Defines the refresh rate options for LiquidGlassView.
This enum allows you to control how often the liquid glass effect updates per second, which can help balance visual smoothness and performance.
Values
| Value | Description |
|---|---|
low | Low refresh rate. |
medium | Medium refresh rate. |
high | High refresh rate. |
deviceRefreshRate | Match the device's system refresh rate if possible. This option attempts to sync the liquid glass effect with the display’s native refresh rate for smoother animations. |
Declared in lib/src/widgets/utils/liquid_glass_refresh_rate.dart
LiquidGlassPosition#
Where a lens sits inside its view. The base type of the two placement strategies below.
Constructors
const LiquidGlassPosition()Methods
| Signature | Description |
|---|---|
Offset resolve(Size parentSize, Size lensSize) |
Declared in lib/src/widgets/utils/liquid_glass_position.dart
Places a lens at an explicit offset from the view's top-left corner.
Constructors
const LiquidGlassOffsetPosition({ this.left, this.top, this.right, this.bottom, })| Parameter | Type | Default | Description |
|---|---|---|---|
left | double? | — | The distance between the lens and the left edge of its parent container. |
top | double? | — | The distance between the lens and the top edge of its parent container. |
right | double? | — | The distance between the lens and the right edge of its parent container. |
bottom | double? | — | The distance between the lens and the bottom edge of its parent container. |
Declared in lib/src/widgets/utils/liquid_glass_position.dart
Places a lens by alignment — centred, pinned to an edge — so it follows the view when the view resizes.
Constructors
const LiquidGlassAlignPosition({ required this.alignment, this.margin = EdgeInsets.zero, // default no margin })| Parameter | Type | Default | Description |
|---|---|---|---|
alignmentrequired | Alignment | — | Defines the alignment of the lens within its parent widget. Examples: - Alignment.center places the lens at the center. - Alignment.topLeft aligns it to the top-left corner. - Alignment.bottomRight aligns it to the bottom-right corner. |
margin | EdgeInsets | EdgeInsets.zero | The outer margin (spacing) around the lens. This margin is applied outside the lens boundaries, creating space between the lens and surrounding UI elements. |
Declared in lib/src/widgets/utils/liquid_glass_position.dart
Comments
Comments are GitHub Discussions — reply from either place.