Liquid Glass Easy

Morph

Glass that measures its child and flows to the new size.

LiquidGlassMorph is a sheet of glass that morphs to fit whatever you put in it. Swap the child and the glass measures the new one and flows to its size — you do not tell it how big to be:

Dart
LiquidGlassMorph(
  alignment: Alignment.bottomRight,
  motion: LiquidGlassMorphMotion.fluid,
  child: open
      ? const Menu(key: ValueKey('menu'))
      : const Icon(Icons.more_horiz, key: ValueKey('dots')),
)

That is the whole API. Add a row to Menu and the glass grows to match, because there is only one truth about how big the menu is and the glass reads it rather than being told it a second time.

ParameterDefaultWhat it does
childThe content, and the thing that decides how big the glass is. Give your children keys — a child that keeps its type is not seen as new, so it neither cross-fades nor re-measures.
alignmentcenterWhich edge or corner holds while the surface changes size. The parameter people leave at centre and later regret — see below.
motionfluidThe physics. Presets: fluid, anchoredPop, droplet, calm, and plain — a single-lens spring with no second blob at all.
styleLiquidGlassStyle()The material. Its shape is the destination's corner curve and radius; unset, the surface is a capsule. Blur and refraction thicken as the glass grows.
smoothness40Peak radius of the neck between the two blobs mid-morph — the same quantity as the blender's. Nothing while they coincide, so the resting shape is never inflated by it.
width / heightnullOverrides for an axis you genuinely own — a fixed-width menu whose height varies. Set one and that axis is pinned while the other is still measured.
onEndnullFires once the springs settle.
debugClipBoundsfalseOutlines the backdrop-pass region in magenta. Diagnostic only — it costs performance.

How it moves#

The morph is two blobs of one liquid, rendered as a single surface by the same machinery a blend uses. The new shape is born inside the old one and grows around its anchor; the old shape lingers, then drains into it. While they overlap they are joined by the smooth union, so mid-morph the outline has a waist — which is what a resize can never have and what the eye reads as liquid. At rest the blobs coincide and the union is switched off, so the resting shape is exact.

Each blob keeps its own corner curve and radius — the old shape is drawn as the old shape until it is gone. Content is not stretched with the glass: the old child blurs, fades and scales out, the new one in, both on the morph's own clock, and a swap that reverses mid-flight carries each child on from wherever it had got to. LiquidGlassMorphMotion.plain — or any preset with copyWith(blended: false) — trades the waist for a single lens whose outline springs between the shapes: no neck, no drain, one backdrop pass.

Alignment is the contract#

The widget fills the box it is given and places the glass inside it by alignment — because a size on its own does not say which way a surface should grow. Centred, both edges move, and every direction looks correct until you move the control to a corner. At Alignment.bottomRight that corner holds and the surface opens up and to the left, the way a toolbar control has to. Alignment is continuous — an axis at x sends (1 + x) / 2 of any growth out one side — and for a surface placed by something that is not an alignment, LiquidGlassMorph.alignmentFor(rect, field) recovers the alignment its own rect implies.

Comments

Comments are GitHub Discussions — reply from either place.