Liquid Glass Easy

Batch

Any number of lenses, one read of the backdrop — on Impeller.

A lens on Impeller works by reading back what is already painted behind it and running the glass shader over that copy. The read is the expensive part, and it is per lens: twenty glass cards in a list mean twenty readbacks a frame, forty with blur. Wrap them in a LiquidGlassBatch and they are all tagged with one shared backdrop key, so the engine takes the copy once and every member samples it.

Dart
LiquidGlassBatch(
  child: ListView.builder(
    itemCount: 40,
    itemBuilder: (context, i) => LiquidGlassLens(
      style: cardStyle,
      child: Card(i),
    ),
  ),
)

Nothing else about the members changes: each keeps its own shape, its own style, its own child, its own adaptivity, its own touch response. They are still separate sheets of glass — the batch only makes them share the read. Members are found by context, so they can sit at any depth, and the components join the same way: a button, a FAB, an app bar, the tab bar's capsule, a LiquidGlassSheet placed in the tree — any of them inside a batch shares its read with no wiring at all. The one exception is what showLiquidGlassSheet and showLiquidGlassDialog present: that is a route, outside every batch's tree, so it takes a read of its own unless the call asks to join one — see the note below.

ParameterDefaultWhat it does
childThe subtree whose LiquidGlassLens descendants share the read.
enabledtrueFlip to false and every lens goes back to a read of its own — the pre-batch behaviour, and the A/B for measuring what the batch saves.

Batch or blender?#

A blender draws its members with one shader: one silhouette, one material, one read — and at most eight members, because everything it draws has to fit in that shader. With smoothness: 0 it does not even fuse them; it is one pass for the set, which is what the deprecated LiquidGlassGroup was. The batch shares nothing but the read. Members stay separate passes, keep their own looks, and there is no limit on how many there can be. Reach for the blender when the lenses should read as one piece of glass, and for the batch when there are simply a lot of them. The two nest: a blender inside a batch is one member like any other, drawn from the batch's copy.

The one rule: members must not overlap#

Every member samples the same copy, taken before any of them painted — so a member cannot see another member's glass. Where two overlap, the one on top refracts what was behind the one below, and the stack reads as a single lens. Lists, grids and rows of controls never trip this. A lens that must overlap another belongs out of the batch:

Dart
LiquidGlassBatch(
  child: Column(
    children: [
      Expanded(child: cards),          // shared read
      LiquidGlassBatch.exclude(        // its own read again
        child: myTabBar,
      ),
    ],
  ),
)

Where it applies#

The shared key is an Impeller mechanism, so the batch is what changes the cost there. On the Skia / web capture path a LiquidGlassView already captures its background once for every lens inside it, so the batch is inert rather than wrong — the same tree runs on both backends.

One layer of glass, floating over the page#

The batch exists because a backdrop read is the expensive thing Impeller does, and sharing it is the single biggest saving available on that engine — many lenses for the price of one. But it is worth saying what the batch is for, because the layouts it makes cheap are also the layouts that are right.

Glass is chrome. In almost every case it should be one layer floating over the page — a bar, a set of controls, a row of actions — with the content passing underneath it. That is what the material is for: you are meant to see what is under the glass, which is why it refracts rather than covers.

Comments

Comments are GitHub Discussions — reply from either place.