liquid_reveal 0.1.0 copy "liquid_reveal: ^0.1.0" to clipboard
liquid_reveal: ^0.1.0 copied to clipboard

A liquid page reveal that measures its own box instead of the screen, so it works inside a card, a list row or a full page. Render object, no rebuilds.

liquid_reveal #

A liquid page reveal that measures its own box, not the screen. So the same widget is a full-screen onboarding flow, a 220 pixel hero card or an 84 pixel list row, and feels the same in each.

LiquidReveal(
  itemCount: pages.length,
  itemBuilder: (BuildContext context, int index) => pages[index],
)

Drag it either way. Forwards, the next page grows from one edge; backwards, the previous page grows from the other. The wave follows your finger and lets go into place.

Why another one #

The liquid swipe effect has been around for years, and every implementation of it divides by MediaQuery.of(context).size — the screen. Put one in a card and the numbers stop meaning anything: a drag across a 200 pixel box is measured against an 800 pixel screen, so the reveal creeps along at a quarter speed and the commit threshold is never reached.

Here nothing asks how big the screen is. The render object measures itself:

  • a drag of half the box is half the reveal, in a card or on a page;
  • the wave's depth and width are fractions of the box, so it looks the same at any size;
  • the commit threshold is a fraction of the box too.

What it costs #

One RenderBox. The drag and the settle animation only ever call markNeedsPaint, so no widget rebuilds while a reveal runs — the builder is called again exactly once, when the page changes. Two pages are laid out at a time, however long the list is.

Settings #

LiquidReveal(
  settings: const LiquidRevealSettings(
    side: RevealSide.right,        // or left, top, bottom
    duration: Duration(milliseconds: 550),
    curve: Curves.easeOutCubic,    // while settling; a drag is never curved
    waveAmplitude: 0.18,           // fraction of the travel
    waveSpread: 0.45,              // fraction of the edge
    commitAt: 0.35,                // let go past this and the page turns
    flingVelocity: 700,            // px/s that commits whatever commitAt says
    dragEdgeFraction: null,        // null: drag anywhere; 0.25: only the edge
    loop: true,
    allowReverse: true,            // a drag the other way goes back a page
    ignoreDragWhileSettling: true,
  ),
  itemCount: 3,
  itemBuilder: builder,
)

LiquidRevealSettings.soft is a wide shallow wave for a hero card; .sharp is narrow and deep, closer to the original effect.

dragEdgeFraction is the one to reach for when the pages have their own horizontal gestures. Without it a drag anywhere in the box turns the page, which is what a carousel wants and the last thing a page with a chip row wants.

From code #

final LiquidRevealController controller = LiquidRevealController();

LiquidReveal(controller: controller, itemCount: 3, itemBuilder: builder);

controller.next();          // reveal the next page
controller.previous();      // reveal the previous one, from the other edge
controller.page;            // the page at the front
controller.progress;        // 0 to 1, the reveal in flight
controller.offset;          // page + progress, for a sliding indicator

The controller is a Listenable, so an indicator can watch it while the pages stay still:

ListenableBuilder(
  listenable: controller,
  builder: (BuildContext context, Widget? _) => Dots(at: controller.offset),
)

The wave #

The boundary between the two pages is a pure function — no widget, no context — which is why it can be tested rather than eyeballed:

final LiquidWave wave = LiquidWave(
  progress: 0.4,
  size: const Size(300, 200),
  side: RevealSide.right,
  focus: 120,          // where the finger is, along the edge
  amplitude: 54,
  spread: 90,
);

wave.boundaryAt(120);  // how far the boundary has come, at that point
wave.toPath();         // the revealed region

A straight line sweeps across the box, pulled towards the incoming page by a raised cosine centred on the finger. The bump's envelope is zero at both ends of the gesture, so a finished reveal has a straight edge rather than a frozen ripple, and it reaches exactly zero at spread — the boundary is provably flat outside the bump.

When the finger is near a corner, half the wave falls outside the box and you see a fin rather than a blob. That is deliberate: the wave follows the finger instead of sliding away from it.

Both directions #

A drag decides its direction from the first movement, and cannot change its mind until the reveal is back at zero — so a gesture that turns round mid-flight undoes itself rather than leaving the wrong page underneath. Backwards is allowReverse, on by default; turn it off for a flow that should only go one way.

Up to three pages are built: the one at the front, the next and the previous. Never more, however long the list is.

What it will not do #

  • A vertical reveal inside a vertical scroll view loses the gesture. Flutter's arena gives a vertical drag to the ListView, and there is nothing a child can do about it. Use a horizontal side inside a scrolling page, put the vertical one somewhere that does not scroll, or drive it with the controller.
  • No jumping to an arbitrary page. The reveal is a transition between two neighbours; jumpTo(7) would be a cut, not a reveal.
  • It is not liquid_swipe. No code is shared with it. If you want that package's full-screen behaviour and its slide icon, use that package.

Example #

/example shows the same widget at three sizes with one set of settings, a dot indicator driven by the controller, and the four sides.

cd example && flutter run

License #

MIT.

0
likes
160
points
112
downloads

Documentation

API reference

Publisher

verified publisherbomsamdi.com

Weekly Downloads

A liquid page reveal that measures its own box instead of the screen, so it works inside a card, a list row or a full page. Render object, no rebuilds.

Repository (GitHub)
View/report issues

Topics

#animation #transition #render-object #carousel #onboarding

License

MIT (license)

Dependencies

flutter

More

Packages that depend on liquid_reveal