fluid_carousel 🌊

pub package likes popularity license

A modern, buttery-smooth, and extensible carousel widget for Flutter. Designed as a drop-in 10x upgrade to legacy carousel packages with zero-rebuild reactive indicators, Impeller-optimized 3D/parallax transformers, desktop & web gesture parity, and native Sliver support.


Most Flutter carousel packages were created during the Flutter 1.x/2.x eras and carry significant technical debt:

Feature carousel_slider fluid_carousel
Infinite Looping Artificial 10,000 index hack; duplicate key crashes when itemCount < 3 Seamless circular indexing with safe slot isolation
Indicators Requires parent setState() causing full page rebuilds Zero parent rebuilds; reactive ValueListenable with continuous sub-pixel morphing
Visual Transformers Hardcoded boolean enlargeCenterPage Pluggable transformers: Scale, 3D Coverflow, Parallax, Card Stack
Desktop & Web Clunky; mouse drag disabled by default Native trackpad flings, mouse drag, and keyboard arrow navigation
Impeller Ready Frequent unconstrained canvas repaints Built-in RepaintBoundary isolation for stable 120 FPS
Slivers Requires rigid SliverToBoxAdapter wrapping First-class SliverFluidCarousel

Getting Started

Add fluid_carousel to your pubspec.yaml:

dependencies:
  fluid_carousel: ^0.1.0

Import the package in your Dart code:

import 'package:fluid_carousel/fluid_carousel.dart';

Quick Example

import 'package:flutter/material.dart';
import 'package:fluid_carousel/fluid_carousel.dart';

class SimpleCarouselExample extends StatelessWidget {
  const SimpleCarouselExample({super.key});

  @override
  Widget build(BuildContext context) {
    return FluidCarousel(
      itemCount: 5,
      options: const FluidCarouselOptions(
        viewportFraction: 0.85,
        autoPlay: true,
      ),
      itemBuilder: (context, index) {
        return Card(
          color: Colors.primaries[index % Colors.primaries.length],
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
          child: Center(
            child: Text(
              'Slide #$index',
              style: const TextStyle(fontSize: 24, color: Colors.white, fontWeight: FontWeight.bold),
            ),
          ),
        );
      },
    );
  }
}

3D & Cinematic Visual Transformers

Switch between beautiful prebuilt visual effects with a single parameter or create your own custom transformer:

1. 3D Coverflow

Perspective depth and Y-axis rotation:

FluidCarousel(
  itemCount: items.length,
  transformer: const FluidCoverflowTransformer(
    maxRotationAngle: 0.35,
    depthOffset: 60.0,
    perspective: 0.0015,
  ),
  itemBuilder: (context, index) => MyCard(items[index]),
)

2. Cinematic Parallax

Inner card content translates in counter-direction to scroll:

FluidCarousel(
  itemCount: items.length,
  transformer: const FluidParallaxTransformer(
    parallaxFactor: 0.4,
    minScale: 0.9,
  ),
  itemBuilder: (context, index) => MyCard(items[index]),
)

3. Layered Card Stack / Deck

Upcoming cards tuck behind the front card like an Apple Wallet deck:

FluidCarousel(
  itemCount: items.length,
  transformer: const FluidCardStackTransformer(
    stackOffset: 24.0,
    scaleStep: 0.08,
    maxVisibleCards: 3,
  ),
  itemBuilder: (context, index) => MyCard(items[index]),
)

4. Custom Transformer

FluidCarousel(
  itemCount: items.length,
  transformer: FluidBuilderTransformer((context, child, info) {
    // info.position is 0.0 at center, 1.0 one page to the right, -1.0 to the left
    return Transform.scale(
      scale: 1.0 - (info.position.abs() * 0.2),
      child: child,
    );
  }),
  itemBuilder: (context, index) => MyCard(items[index]),
)

Zero-Rebuild Reactive Indicators

Unlike traditional packages that require calling setState() inside onPageChanged, FluidIndicator connects directly to FluidCarouselController.pageOffset. It animates smoothly with sub-pixel precision while dragging—without rebuilding your parent widget tree.

class ScreenWithIndicator extends StatefulWidget {
  const ScreenWithIndicator({super.key});

  @override
  State<ScreenWithIndicator> createState() => _ScreenWithIndicatorState();
}

class _ScreenWithIndicatorState extends State<ScreenWithIndicator> {
  late final FluidCarouselController _controller;

  @override
  void initState() {
    super.initState();
    _controller = FluidCarouselController();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        FluidCarousel(
          controller: _controller,
          itemCount: 5,
          itemBuilder: (context, index) => MyCard(index),
        ),
        const SizedBox(height: 16),
        // Reactively morphs during user drag with 0 parent rebuilds!
        FluidIndicator(
          controller: _controller,
          count: 5,
          effect: const FluidWormEffect(
            activeColor: Colors.deepPurpleAccent,
            dotWidth: 10,
            dotHeight: 10,
          ),
        ),
      ],
    );
  }
}

Prebuilt Indicator Effects

  • FluidWormEffect: Active dot stretches organically like an elastic worm between positions.
  • FluidExpandingDotsEffect: Active dot expands into an elegant pill shape while dragging.
  • FluidSlidingBarEffect: Modern progress bar track with continuous sliding thumb.

Programmatic Controls

Manipulate the carousel programmatically via FluidCarouselController:

// Animate to specific page
await controller.animateToPage(3, duration: const Duration(milliseconds: 500));

// Snap to page immediately
controller.jumpToPage(1);

// Step next / previous
await controller.nextPage();
await controller.previousPage();

// Pause / Resume AutoPlay
controller.stopAutoPlay();
controller.startAutoPlay();

Native Sliver Integration

Place your carousel directly in a CustomScrollView without layout friction:

CustomScrollView(
  slivers: [
    SliverAppBar(title: const Text('Store')),
    SliverFluidCarousel(
      extent: 240,
      itemCount: banners.length,
      itemBuilder: (context, index) => BannerTile(banners[index]),
    ),
    SliverList(delegate: SliverChildBuilderDelegate(...)),
  ],
)

API Reference

FluidCarouselOptions

Property Type Default Description
viewportFraction double 0.85 Fraction of viewport each card occupies
initialPage int 0 Initial starting item index
loop bool true Infinite circular looping (auto-disabled if itemCount <= 1)
autoPlay bool false Automatic timed page scrolling
autoPlayInterval Duration Duration(seconds: 4) Duration between auto-scroll steps
pauseAutoPlayOnTouch bool true Pauses auto-scroll on pointer drag
pauseAutoPlayOnHover bool true Pauses auto-scroll on mouse cursor hover
scrollDirection Axis Axis.horizontal Carousel orientation (horizontal or vertical)
enableMouseDrag bool true Enables desktop/web mouse dragging
enableKeyboardNavigation bool true Enables Left/Right arrow key navigation
clipBehavior Clip Clip.antiAlias Content clipping behavior
onPageChanged Function null Callback when settled page index changes

Contributing

Contributions, issues, and feature requests are welcome! Feel free to check the issues page.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Libraries