fluid_carousel 0.1.0 copy "fluid_carousel: ^0.1.0" to clipboard
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.

example/lib/main.dart

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

void main() {
  runApp(const FluidCarouselExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fluid Carousel Showcase',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.indigo,
        brightness: Brightness.dark,
      ),
      home: const CarouselShowcaseScreen(),
    );
  }
}

enum TransformerType { coverflow, scale, parallax, stack }

enum IndicatorType { worm, expanding, slidingBar }

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

  @override
  State<CarouselShowcaseScreen> createState() => _CarouselShowcaseScreenState();
}

class _CarouselShowcaseScreenState extends State<CarouselShowcaseScreen> {
  late final FluidCarouselController _controller;

  TransformerType _transformerType = TransformerType.coverflow;
  IndicatorType _indicatorType = IndicatorType.worm;
  double _viewportFraction = 0.82;
  bool _autoPlay = false;
  bool _loop = true;

  final List<Map<String, dynamic>> _items = [
    {
      'title': 'Aurora Borealis',
      'category': 'Nature & Exploration',
      'colors': [
        const Color(0xFF0F2027),
        const Color(0xFF203A43),
        const Color(0xFF2C5364)
      ],
      'icon': Icons.nightlight_round,
    },
    {
      'title': 'Solar Flares',
      'category': 'Astrophysics',
      'colors': [const Color(0xFFF12711), const Color(0xFFF5AF19)],
      'icon': Icons.wb_sunny_rounded,
    },
    {
      'title': 'Deep Oceanic Trench',
      'category': 'Marine Biology',
      'colors': [const Color(0xFF1CB5E0), const Color(0xFF000046)],
      'icon': Icons.water_drop_rounded,
    },
    {
      'title': 'Cyber Metropolis',
      'category': 'Future Cities',
      'colors': [
        const Color(0xFF8A2387),
        const Color(0xFFE94057),
        const Color(0xFFF27121)
      ],
      'icon': Icons.location_city_rounded,
    },
    {
      'title': 'Emerald Canopy',
      'category': 'Rainforest Habitats',
      'colors': [const Color(0xFF134E5E), const Color(0xFF71B280)],
      'icon': Icons.park_rounded,
    },
  ];

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

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

  FluidTransformer get _activeTransformer {
    switch (_transformerType) {
      case TransformerType.coverflow:
        return const FluidCoverflowTransformer(
          maxRotationAngle: 0.35,
          depthOffset: 60.0,
          perspective: 0.0015,
        );
      case TransformerType.scale:
        return const FluidScaleTransformer(
          minScale: 0.82,
          minOpacity: 0.5,
        );
      case TransformerType.parallax:
        return const FluidParallaxTransformer(
          parallaxFactor: 0.4,
          minScale: 0.9,
        );
      case TransformerType.stack:
        return const FluidCardStackTransformer(
          stackOffset: 24.0,
          scaleStep: 0.08,
          maxVisibleCards: 3,
        );
    }
  }

  FluidIndicatorEffect get _activeIndicatorEffect {
    switch (_indicatorType) {
      case IndicatorType.worm:
        return const FluidWormEffect(
          activeColor: Colors.amberAccent,
          color: Colors.white24,
          dotWidth: 12.0,
          dotHeight: 12.0,
          spacing: 10.0,
        );
      case IndicatorType.expanding:
        return const FluidExpandingDotsEffect(
          activeColor: Colors.cyanAccent,
          color: Colors.white24,
          dotWidth: 10.0,
          dotHeight: 10.0,
          expansionFactor: 3.2,
          spacing: 8.0,
        );
      case IndicatorType.slidingBar:
        return const FluidSlidingBarEffect(
          activeColor: Colors.pinkAccent,
          color: Colors.white24,
          trackWidth: 140.0,
          dotHeight: 6.0,
        );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Fluid Carousel Showcase',
            style: TextStyle(fontWeight: FontWeight.bold)),
        elevation: 0,
        actions: [
          IconButton(
            tooltip: 'Previous Page',
            icon: const Icon(Icons.arrow_back_ios_new_rounded),
            onPressed: () => _controller.previousPage(),
          ),
          IconButton(
            tooltip: 'Next Page',
            icon: const Icon(Icons.arrow_forward_ios_rounded),
            onPressed: () => _controller.nextPage(),
          ),
          const SizedBox(width: 8),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.symmetric(vertical: 20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Sub-pixel live telemetry banner
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 20),
              child: Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.white.withValues(alpha: 0.05),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: Colors.white12),
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: [
                    ValueListenableBuilder<int>(
                      valueListenable: _controller.currentPage,
                      builder: (context, page, _) {
                        return Column(
                          children: [
                            const Text('SETTLED PAGE',
                                style: TextStyle(
                                    fontSize: 10,
                                    color: Colors.white54,
                                    letterSpacing: 1.1)),
                            Text('$page / ${_items.length - 1}',
                                style: const TextStyle(
                                    fontWeight: FontWeight.bold, fontSize: 16)),
                          ],
                        );
                      },
                    ),
                    ValueListenableBuilder<double>(
                      valueListenable: _controller.pageOffset,
                      builder: (context, offset, _) {
                        return Column(
                          children: [
                            const Text('SUB-PIXEL OFFSET',
                                style: TextStyle(
                                    fontSize: 10,
                                    color: Colors.white54,
                                    letterSpacing: 1.1)),
                            Text(offset.toStringAsFixed(3),
                                style: const TextStyle(
                                    fontWeight: FontWeight.bold,
                                    fontSize: 16,
                                    color: Colors.amberAccent)),
                          ],
                        );
                      },
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 24),

            // Fluid Carousel Widget
            SizedBox(
              height: 260,
              child: FluidCarousel(
                key:
                    ValueKey('carousel_${_transformerType}_$_viewportFraction'),
                controller: _controller,
                itemCount: _items.length,
                transformer: _activeTransformer,
                options: FluidCarouselOptions(
                  viewportFraction: _viewportFraction,
                  loop: _loop,
                  autoPlay: _autoPlay,
                  enableMouseDrag: true,
                  enableKeyboardNavigation: true,
                ),
                itemBuilder: (context, index) {
                  final item = _items[index];
                  return Container(
                    margin: const EdgeInsets.symmetric(horizontal: 8),
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(24),
                      gradient: LinearGradient(
                        colors: item['colors'] as List<Color>,
                        begin: Alignment.topLeft,
                        end: Alignment.bottomRight,
                      ),
                      boxShadow: [
                        BoxShadow(
                          color: (item['colors'] as List<Color>)
                              .first
                              .withValues(alpha: 0.4),
                          blurRadius: 18,
                          offset: const Offset(0, 8),
                        ),
                      ],
                    ),
                    padding: const EdgeInsets.all(24),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            Text(
                              item['category'] as String,
                              style: const TextStyle(
                                  color: Colors.white70,
                                  fontSize: 12,
                                  fontWeight: FontWeight.w600,
                                  letterSpacing: 1.2),
                            ),
                            Icon(item['icon'] as IconData,
                                color: Colors.white, size: 28),
                          ],
                        ),
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              item['title'] as String,
                              style: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 22,
                                  fontWeight: FontWeight.bold),
                            ),
                            const SizedBox(height: 4),
                            Text(
                              'Card index #$index',
                              style: const TextStyle(
                                  color: Colors.white60, fontSize: 13),
                            ),
                          ],
                        ),
                      ],
                    ),
                  );
                },
              ),
            ),
            const SizedBox(height: 20),

            // Zero-Rebuild Reactive Indicator
            Center(
              child: FluidIndicator(
                controller: _controller,
                count: _items.length,
                effect: _activeIndicatorEffect,
              ),
            ),
            const SizedBox(height: 32),

            // Configuration Controls
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 20),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text('VISUAL TRANSFORMER',
                      style: TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 13,
                          letterSpacing: 1.1)),
                  const SizedBox(height: 8),
                  SegmentedButton<TransformerType>(
                    segments: const [
                      ButtonSegment(
                          value: TransformerType.coverflow,
                          label: Text('3D Coverflow')),
                      ButtonSegment(
                          value: TransformerType.scale, label: Text('Scale')),
                      ButtonSegment(
                          value: TransformerType.parallax,
                          label: Text('Parallax')),
                      ButtonSegment(
                          value: TransformerType.stack, label: Text('Stack')),
                    ],
                    selected: {_transformerType},
                    onSelectionChanged: (selected) {
                      setState(() => _transformerType = selected.first);
                    },
                  ),
                  const SizedBox(height: 20),
                  const Text('INDICATOR STYLE',
                      style: TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 13,
                          letterSpacing: 1.1)),
                  const SizedBox(height: 8),
                  SegmentedButton<IndicatorType>(
                    segments: const [
                      ButtonSegment(
                          value: IndicatorType.worm,
                          label: Text('Elastic Worm')),
                      ButtonSegment(
                          value: IndicatorType.expanding,
                          label: Text('Expanding Dots')),
                      ButtonSegment(
                          value: IndicatorType.slidingBar,
                          label: Text('Sliding Bar')),
                    ],
                    selected: {_indicatorType},
                    onSelectionChanged: (selected) {
                      setState(() => _indicatorType = selected.first);
                    },
                  ),
                  const SizedBox(height: 20),
                  Row(
                    children: [
                      Expanded(
                        child: Text(
                          'Viewport Fraction: ${_viewportFraction.toStringAsFixed(2)}',
                          style: const TextStyle(fontWeight: FontWeight.w600),
                        ),
                      ),
                      Slider(
                        value: _viewportFraction,
                        min: 0.6,
                        max: 1.0,
                        divisions: 8,
                        onChanged: (val) =>
                            setState(() => _viewportFraction = val),
                      ),
                    ],
                  ),
                  SwitchListTile(
                    title:
                        const Text('Auto-Play (Pauses on Touch & Mouse Hover)'),
                    value: _autoPlay,
                    onChanged: (val) => setState(() => _autoPlay = val),
                  ),
                  SwitchListTile(
                    title: const Text('Infinite Circular Looping'),
                    value: _loop,
                    onChanged: (val) => setState(() => _loop = val),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
160
points
323
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A modern, fluid, and extensible carousel widget for Flutter with zero-rebuild reactive indicators, 3D/parallax transformers, desktop gestures, and sliver support.

Repository (GitHub)
View/report issues

Topics

#carousel #slider #animation #ui #widget

License

MIT (license)

Dependencies

flutter

More

Packages that depend on fluid_carousel