Skip to content
Blog

Liquid Swipe Page Transitions in Flutter with liquid_swipe

Use liquid_swipe 3.1.0's LiquidSwipe widget for wavy full-screen reveals: waveType options, LiquidController, callbacks, and when PageView is the better fit.

Published on • November 5, 2026

AI Assistant

A liquid swipe turns paging into a gooey, wave-edged reveal that feels handmade rather than system-default. liquid_swipe has shipped that effect as a single widget for years — it is brilliant for onboarding, and the wrong tool almost everywhere else.

What liquid_swipe gives you

The package sits at 3.1.0, published March 29, 2023 by the verified sahdeepsingh.com publisher under Apache-2.0, with provider ^6.0.0 as its only dependency. It builds on Android, iOS, Linux, macOS, web and Windows, and the changelog shows 3.0.0 added Flutter 3.0 support while 3.1.0 fixed issues #83 and #85 — a stable but quiet package (well under 200 weekly downloads). It is inspired by Cuberto’s Swift implementation, though the README includes a disclaimer that the project is not affiliated with them.

Building the transition

You hand it a list of pages; each page is any widget, typically a full-screen Container with its own background colour:

final pages = [
  Container(color: const Color(0xFFF97316), child: const Center(child: Text('Step one'))),
  Container(color: const Color(0xFF0EA5E9), child: const Center(child: Text('Step two'))),
  Container(color: const Color(0xFF22C55E), child: const Center(child: Text('Step three'))),
];

@override
Widget build(BuildContext context) {
  return LiquidSwipe(
    pages: pages,
    initialPage: 0,
    enableLoop: true,
    fullTransitionValue: 400, // lower = faster swipe animation
    waveType: WaveType.circularReveal,
    onPageChangeCallback: (index) => debugPrint('now on page $index'),
  );
}

WaveType has exactly two values — WaveType.liquidReveal (the default wavy clipper) and WaveType.circularReveal — so swapping the feel of your transition is one line. fullTransitionValue defaults to 400.0 and governs swipe sensitivity: lower it to complete a swipe over a shorter drag distance.

Driving it with LiquidController

For imperative control, create a LiquidController in initState, pass it as liquidController, and only use it after the first build (the docs warn against calling methods earlier). It mirrors the familiar PageController verbs:

late final LiquidController _controller = LiquidController();

// after build:
_controller.animateToPage(page: 2, duration: 600); // duration covers ALL pages
_controller.jumpToPage(page: 0);                    // no animation
final current = _controller.currentPage;            // getter, default 0
_controller.shouldDisableGestures(disable: true);   // lock swiping at runtime

Note the subtlety in animateToPage: its duration is the total time to traverse the intermediate pages, not per page. isUserGestureDisabled reports the current gesture lock state.

Callbacks, icons and gesture control

Pages are styled independently, so each panel can carry its own gradient, image or full-screen artwork — the clipper only reveals what you built:

Container(
  decoration: const BoxDecoration(
    gradient: LinearGradient(
      colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)],
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
    ),
  ),
  child: const Center(child: Text('Ship your first map in minutes')),
)

Three callbacks cover most needs: onPageChangeCallback when the page index changes, slidePercentCallback on every frame of the swipe animation, and currentUpdateTypeCallback for UpdateType transitions. The README explicitly cautions that slidePercentCallback is “quite frequent on swipe”, so keep its body trivial — no layout or IO per frame.

For affordances, pass slideIconWidget (default null) and position it with positionSlideIcon, a double between 0.0 and 1.0 defaulting to 0.8. If you are upgrading from v1, note that enableSlideIcon was removed in v2.0.0 — a non-null slideIconWidget is the switch now. Gesture policy comes from ignoreUserGestureWhileAnimating (default false, set true to block drags mid-animation), disableUserGesture, enableSideReveal (peek of the next page) and preferDragFromRevealedArea.

When a liquid swipe is the wrong tool

The effect is a full-screen clipper reveal: during a drag, both the current and next page are on screen and the clip path is recomputed continuously, which is fine for three or four onboarding panels but costly as a general navigation pattern. Beyond the render cost, LiquidSwipe has no route semantics — there is no back button, no deep link and no system back gesture, so app-level navigation belongs in PageView with a custom PageScrollPhysics, or a PageRouteBuilder with a CustomClipper if you want the look on a single transition. Reach for liquid_swipe when the screens are disposable, ordered and optional; reach for routes and PageView when users must be able to backtrack.

Takeaway: keep LiquidSwipe for its strengths — a 3.1.0 widget with waveType, LiquidController and onPageChangeCallback makes three or four onboarding panels feel special in a few lines — and leave real navigation, back gestures and state preservation to PageView and routes.