fluid_carousel 0.1.0
fluid_carousel: ^0.1.0 copied to clipboard
A modern, fluid, and extensible carousel widget for Flutter with zero-rebuild reactive indicators, 3D/parallax transformers, desktop gestures, and sliver support.
fluid_carousel 🌊 #
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.
Why fluid_carousel? #
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.