Skip to main content

flip_card_swiper 2.2.0 copy "flip_card_swiper: ^2.2.0" to clipboard
flip_card_swiper: ^2.2.0 copied to clipboard

A customizable, swipeable card widget with smooth flip animations and haptic support.

example/lib/main.dart

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

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flip Card Swiper Examples',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true),
      home: const ExamplesHome(),
    );
  }
}

// ─────────────────────────────────────────────────────────────
// Home screen
// ─────────────────────────────────────────────────────────────

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

  static const _examples = [
    _ExampleEntry(
      title: 'Basic',
      subtitle: 'Default setup · simple coloured cards',
      icon: Icons.layers,
      color: Color(0xFF546E7A),
      page: BasicExample(),
    ),
    _ExampleEntry(
      title: 'Credit Cards',
      subtitle: 'Rounded corners · gradient · no edge',
      icon: Icons.credit_card,
      color: Color(0xFF1A237E),
      page: CreditCardsExample(),
    ),
    _ExampleEntry(
      title: 'Flash Cards',
      subtitle: 'Tap to reveal · fast flip · high perspective',
      icon: Icons.school,
      color: Color(0xFF1B5E20),
      page: FlashCardsExample(),
    ),
    _ExampleEntry(
      title: 'Travel Destinations',
      subtitle: 'Dramatic 3-D · thick edge · slow reveal',
      icon: Icons.flight_takeoff,
      color: Color(0xFF4A148C),
      page: TravelCardsExample(),
    ),
    _ExampleEntry(
      title: 'Profile Cards',
      subtitle: 'Social / dating style · avatar · tags',
      icon: Icons.person,
      color: Color(0xFF880E4F),
      page: ProfileCardsExample(),
    ),
    _ExampleEntry(
      title: 'Minimal News',
      subtitle: 'Flat design · snappy animation · no edge',
      icon: Icons.article,
      color: Color(0xFF212121),
      page: MinimalCardsExample(),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFF5F5F5),
      appBar: AppBar(
        title: const Text('Flip Card Swiper'),
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
      ),
      body: ListView.separated(
        padding: const EdgeInsets.all(16),
        itemCount: _examples.length,
        separatorBuilder: (_, __) => const SizedBox(height: 12),
        itemBuilder: (context, i) {
          final e = _examples[i];
          return _ExampleTile(entry: e);
        },
      ),
    );
  }
}

class _ExampleEntry {
  final String title;
  final String subtitle;
  final IconData icon;
  final Color color;
  final Widget page;
  const _ExampleEntry({
    required this.title,
    required this.subtitle,
    required this.icon,
    required this.color,
    required this.page,
  });
}

class _ExampleTile extends StatelessWidget {
  final _ExampleEntry entry;
  const _ExampleTile({required this.entry});

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.white,
      borderRadius: BorderRadius.circular(16),
      elevation: 2,
      child: InkWell(
        borderRadius: BorderRadius.circular(16),
        onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => entry.page)),
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Row(
            children: [
              Container(
                width: 56,
                height: 56,
                decoration: BoxDecoration(color: entry.color, borderRadius: BorderRadius.circular(14)),
                child: Icon(entry.icon, color: Colors.white, size: 28),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(entry.title, style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16)),
                    const SizedBox(height: 4),
                    Text(entry.subtitle, style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
                  ],
                ),
              ),
              Icon(Icons.chevron_right, color: Colors.grey.shade400),
            ],
          ),
        ),
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────
// Helper
// ─────────────────────────────────────────────────────────────

/// Centers a [FlipCardSwiper] and shows a "current / total" counter driven by
/// [FlipCardSwiper.onCardChange].
class SwiperDemoBody extends StatefulWidget {
  const SwiperDemoBody({super.key, required this.itemCount, required this.builder, this.dark = false});

  final int itemCount;
  final bool dark;
  final Widget Function(ValueChanged<int> onCardChange) builder;

  @override
  State<SwiperDemoBody> createState() => _SwiperDemoBodyState();
}

class _SwiperDemoBodyState extends State<SwiperDemoBody> {
  int _index = 0;

  @override
  void didUpdateWidget(SwiperDemoBody oldWidget) {
    super.didUpdateWidget(oldWidget);
    // A new deck restarts at its first card (onCardChange is not called for that).
    if (oldWidget.itemCount != widget.itemCount) _index = 0;
  }

  @override
  Widget build(BuildContext context) {
    final Color fg = widget.dark ? Colors.white70 : Colors.black54;
    return Column(
      children: [
        Expanded(child: Center(child: widget.builder((i) => setState(() => _index = i)))),
        Padding(
          padding: const EdgeInsets.only(bottom: 48),
          child: Text(
            widget.itemCount == 0 ? 'No cards' : '${_index + 1} / ${widget.itemCount}',
            key: const Key('card-counter'),
            style: TextStyle(
              color: fg,
              fontSize: 13,
              fontWeight: FontWeight.w600,
              letterSpacing: 1,
              fontFeatures: const [FontFeature.tabularFigures()],
            ),
          ),
        ),
      ],
    );
  }
}

// ─────────────────────────────────────────────────────────────
// 0. Basic (original)
// ─────────────────────────────────────────────────────────────

/// Also shows changing [FlipCardSwiper.cardData] at runtime: remove cards down to
/// none, then restore the deck. A new list resets the stack to its first card.
class BasicExample extends StatefulWidget {
  const BasicExample({super.key});

  @override
  State<BasicExample> createState() => _BasicExampleState();
}

class _BasicExampleState extends State<BasicExample> {
  static final List<Map<String, dynamic>> _all = [
    {'color': Colors.blue, 'text': 'Card 1'},
    {'color': Colors.red, 'text': 'Card 2'},
    {'color': Colors.green, 'text': 'Card 3'},
    {'color': Colors.purple, 'text': 'Card 4'},
    {'color': Colors.orange, 'text': 'Card 5'},
    {'color': Colors.teal, 'text': 'Card 6'},
    {'color': Colors.pink, 'text': 'Card 7'},
    {'color': Colors.amber, 'text': 'Card 8'},
    {'color': Colors.indigo, 'text': 'Card 9'},
    {'color': Colors.brown, 'text': 'Card 10'},
  ];

  List<Map<String, dynamic>> _cards = _all;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFEEEEEE),
      appBar: AppBar(
        title: const Text('Basic'),
        backgroundColor: const Color(0xFF546E7A),
        foregroundColor: Colors.white,
        actions: [
          IconButton(
            key: const Key('remove-card'),
            tooltip: 'Remove last card',
            icon: const Icon(Icons.remove),
            onPressed: _cards.isEmpty ? null : () => setState(() => _cards = _cards.sublist(0, _cards.length - 1)),
          ),
          IconButton(
            key: const Key('reset-cards'),
            tooltip: 'Restore all cards',
            icon: const Icon(Icons.refresh),
            onPressed: () => setState(() => _cards = _all),
          ),
        ],
      ),
      body: SwiperDemoBody(
        itemCount: _cards.length,
        builder: (onCardChange) => FlipCardSwiper(
          onCardChange: onCardChange,
          cardData: _cards,
          animationDuration: const Duration(milliseconds: 600),
          downDragDuration: const Duration(milliseconds: 200),
          cardEdgeColorBuilder: (index) => darken(_cards[index]['color'] as Color, 0.1),
          cardBuilder: (context, index, visibleIndex) {
            if (index < 0 || index >= _cards.length) {
              return const SizedBox.shrink();
            }
            final card = _cards[index];
            return Container(
              key: ValueKey<int>(index),
              decoration: BoxDecoration(
                color: card['color'] as Color,
                boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.1), blurRadius: 40)],
              ),
              width: 300,
              height: 200,
              alignment: Alignment.center,
              child: Text(card['text'] as String, style: const TextStyle(color: Colors.black45, fontSize: 12)),
            );
          },
        ),
      ),
    );
  }
}

Color darken(Color color, double amount) {
  final hsl = HSLColor.fromColor(color);
  return hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)).toColor();
}

// ─────────────────────────────────────────────────────────────
// 1. Credit Cards
// ─────────────────────────────────────────────────────────────

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

  static const _cards = [
    _CreditCard(
      name: 'Abhay Maurya',
      number: '4532 •••• •••• 7821',
      expiry: '08 / 27',
      gradient: [Color(0xFF1A237E), Color(0xFF283593)],
      network: 'VISA',
    ),
    _CreditCard(
      name: 'Abhay Maurya',
      number: '5412 •••• •••• 3340',
      expiry: '03 / 29',
      gradient: [Color(0xFF880E4F), Color(0xFFAD1457)],
      network: 'MC',
    ),
    _CreditCard(
      name: 'Abhay Maurya',
      number: '3782 •••••• 10005',
      expiry: '11 / 28',
      gradient: [Color(0xFF1B5E20), Color(0xFF2E7D32)],
      network: 'AMEX',
    ),
    _CreditCard(
      name: 'Abhay Maurya',
      number: '6011 •••• •••• 1117',
      expiry: '06 / 30',
      gradient: [Color(0xFFE65100), Color(0xFFF57C00)],
      network: 'DISC',
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D0D0D),
      appBar: AppBar(
        title: const Text('Credit Cards'),
        backgroundColor: const Color(0xFF0D0D0D),
        foregroundColor: Colors.white,
      ),
      body: SwiperDemoBody(
        itemCount: _cards.length,
        dark: true,
        builder: (onCardChange) => FlipCardSwiper(
          onCardChange: onCardChange,
          cardData: _cards,
          animationDuration: const Duration(milliseconds: 700),
          completionPhaseScale: 1.2,
          cardEdgeThickness: 0,
          cardBorderRadius: BorderRadius.circular(20),
          cardBuilder: (context, index, visibleIndex) {
            if (index < 0 || index >= _cards.length) {
              return const SizedBox.shrink();
            }
            final card = _cards[index];
            return Container(
              key: ValueKey(index),
              width: 320,
              height: 195,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(20),
                gradient: LinearGradient(colors: card.gradient, begin: Alignment.topLeft, end: Alignment.bottomRight),
                boxShadow: [
                  BoxShadow(
                    color: card.gradient.first.withValues(alpha: 0.5),
                    blurRadius: 30,
                    offset: const Offset(0, 12),
                  ),
                ],
              ),
              padding: const EdgeInsets.all(24),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      const Text(
                        'BANK',
                        style: TextStyle(
                          color: Colors.white54,
                          fontSize: 11,
                          letterSpacing: 3,
                          fontWeight: FontWeight.w600,
                        ),
                      ),
                      Text(
                        card.network,
                        style: const TextStyle(
                          color: Colors.white,
                          fontWeight: FontWeight.w900,
                          fontSize: 14,
                          letterSpacing: 2,
                        ),
                      ),
                    ],
                  ),
                  const Spacer(),
                  // Chip
                  Container(
                    width: 36,
                    height: 26,
                    decoration: BoxDecoration(color: Colors.amber.shade300, borderRadius: BorderRadius.circular(5)),
                  ),
                  const SizedBox(height: 12),
                  Text(
                    card.number,
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 15,
                      letterSpacing: 2,
                      fontFamily: 'monospace',
                    ),
                  ),
                  const SizedBox(height: 10),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            'CARD HOLDER',
                            style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 9, letterSpacing: 1),
                          ),
                          Text(
                            card.name,
                            style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.w600),
                          ),
                        ],
                      ),
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.end,
                        children: [
                          Text(
                            'EXPIRES',
                            style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 9, letterSpacing: 1),
                          ),
                          Text(card.expiry, style: const TextStyle(color: Colors.white, fontSize: 13)),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

class _CreditCard {
  final String name;
  final String number;
  final String expiry;
  final List<Color> gradient;
  final String network;
  const _CreditCard({
    required this.name,
    required this.number,
    required this.expiry,
    required this.gradient,
    required this.network,
  });
}

// ─────────────────────────────────────────────────────────────
// 2. Flash Cards
// ─────────────────────────────────────────────────────────────

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

  static const _cards = [
    _FlashCard(
      question: 'What is the time\ncomplexity of\nbinary search?',
      answer: 'O(log n)',
      category: 'Algorithms',
      color: Color(0xFF1B5E20),
    ),
    _FlashCard(
      question: 'What does\nDRY stand for?',
      answer: "Don't Repeat\nYourself",
      category: 'Principles',
      color: Color(0xFF0D47A1),
    ),
    _FlashCard(
      question: 'What is a\nclosure?',
      answer: 'A function that\ncaptures its\nsurrounding scope',
      category: 'Concepts',
      color: Color(0xFF4A148C),
    ),
    _FlashCard(
      question: 'Name the four\npillars of OOP',
      answer: 'Encapsulation\nAbstraction\nInheritance\nPolymorphism',
      category: 'OOP',
      color: Color(0xFF880E4F),
    ),
    _FlashCard(
      question: 'What is a\ndeadlock?',
      answer: 'Two or more threads\nwaiting on each\nother indefinitely',
      category: 'Concurrency',
      color: Color(0xFFBF360C),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFF0F4FF),
      appBar: AppBar(
        title: const Text('Flash Cards'),
        backgroundColor: const Color(0xFF1B5E20),
        foregroundColor: Colors.white,
      ),
      body: SwiperDemoBody(
        itemCount: _cards.length,
        builder: (onCardChange) => FlipCardSwiper(
          onCardChange: onCardChange,
          cardData: _cards,
          animationDuration: const Duration(milliseconds: 450),
          downDragDuration: const Duration(milliseconds: 150),
          cardEdgeThickness: 0,
          perspectiveDepth: 0.003,
          cardBuilder: (context, index, visibleIndex) {
            if (index < 0 || index >= _cards.length) {
              return const SizedBox.shrink();
            }
            return _FlashCardView(key: ValueKey(index), card: _cards[index]);
          },
        ),
      ),
    );
  }
}

/// A stateful card: tap to reveal the answer. The swiper keeps a card's State while
/// it stays in the visible stack, so dragging or cancelling never resets it.
class _FlashCardView extends StatefulWidget {
  const _FlashCardView({super.key, required this.card});

  final _FlashCard card;

  @override
  State<_FlashCardView> createState() => _FlashCardViewState();
}

class _FlashCardViewState extends State<_FlashCardView> {
  bool _revealed = false;

  @override
  Widget build(BuildContext context) {
    final card = widget.card;
    return GestureDetector(
      onTap: () => setState(() => _revealed = !_revealed),
      child: Container(
        width: 300,
        height: 200,
        decoration: BoxDecoration(
          color: _revealed ? card.color : Colors.white,
          borderRadius: BorderRadius.circular(16),
          boxShadow: [
            BoxShadow(color: Colors.black.withValues(alpha: 0.08), blurRadius: 20, offset: const Offset(0, 8)),
          ],
        ),
        child: Column(
          children: [
            Container(
              width: double.infinity,
              padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
              decoration: BoxDecoration(
                color: card.color,
                borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
              ),
              child: Text(
                card.category,
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 11,
                  fontWeight: FontWeight.w700,
                  letterSpacing: 1.5,
                ),
              ),
            ),
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Center(
                  child: AnimatedSwitcher(
                    duration: const Duration(milliseconds: 180),
                    child: Text(
                      _revealed ? card.answer : card.question,
                      key: ValueKey(_revealed),
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        fontSize: 17,
                        fontWeight: FontWeight.w600,
                        color: _revealed ? Colors.white : Colors.grey.shade800,
                        height: 1.5,
                      ),
                    ),
                  ),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(bottom: 14),
              child: Text(
                _revealed ? 'Swipe up for the next card' : 'Tap to reveal · swipe up for next',
                style: TextStyle(
                  fontSize: 10,
                  color: _revealed ? Colors.white70 : Colors.grey.shade400,
                  letterSpacing: 0.5,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _FlashCard {
  final String question;
  final String answer;
  final String category;
  final Color color;
  const _FlashCard({required this.question, required this.answer, required this.category, required this.color});
}

// ─────────────────────────────────────────────────────────────
// 3. Travel Destinations
// ─────────────────────────────────────────────────────────────

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

  static const _destinations = [
    _Destination(
      city: 'Kyoto',
      country: 'Japan',
      emoji: '⛩️',
      tagline: 'Ancient temples & bamboo forests',
      gradient: [Color(0xFFB71C1C), Color(0xFF880E4F)],
    ),
    _Destination(
      city: 'Santorini',
      country: 'Greece',
      emoji: '🏛️',
      tagline: 'White-washed villages & Aegean sunsets',
      gradient: [Color(0xFF0277BD), Color(0xFF01579B)],
    ),
    _Destination(
      city: 'Machu Picchu',
      country: 'Peru',
      emoji: '🏔️',
      tagline: 'Lost city of the Incas',
      gradient: [Color(0xFF33691E), Color(0xFF1B5E20)],
    ),
    _Destination(
      city: 'Marrakech',
      country: 'Morocco',
      emoji: '🕌',
      tagline: 'Vibrant souks & riads',
      gradient: [Color(0xFFE65100), Color(0xFFBF360C)],
    ),
    _Destination(
      city: 'Reykjavik',
      country: 'Iceland',
      emoji: '🌌',
      tagline: 'Northern lights & volcanic landscapes',
      gradient: [Color(0xFF1A237E), Color(0xFF4527A0)],
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF121212),
      appBar: AppBar(
        title: const Text('Travel Destinations'),
        backgroundColor: const Color(0xFF121212),
        foregroundColor: Colors.white,
      ),
      body: SwiperDemoBody(
        itemCount: _destinations.length,
        dark: true,
        builder: (onCardChange) => FlipCardSwiper(
          onCardChange: onCardChange,
          cardData: _destinations,
          animationDuration: const Duration(milliseconds: 900),
          completionPhaseScale: 1.35,
          downDragDuration: const Duration(milliseconds: 250),
          perspectiveDepth: 0.003,
          cardEdgeThickness: 10,
          cardEdgeColorBuilder: (i) => darken(_destinations[i].gradient.first, 0.25),
          cardBuilder: (context, index, visibleIndex) {
            if (index < 0 || index >= _destinations.length) {
              return const SizedBox.shrink();
            }
            final dest = _destinations[index];
            return Container(
              key: ValueKey(index),
              width: 320,
              height: 210,
              decoration: BoxDecoration(
                gradient: LinearGradient(colors: dest.gradient, begin: Alignment.topLeft, end: Alignment.bottomRight),
                boxShadow: [
                  BoxShadow(
                    color: dest.gradient.first.withValues(alpha: 0.4),
                    blurRadius: 40,
                    offset: const Offset(0, 16),
                  ),
                ],
              ),
              padding: const EdgeInsets.all(28),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(dest.emoji, style: const TextStyle(fontSize: 40)),
                  const Spacer(),
                  Text(
                    dest.city,
                    style: const TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.w800, height: 1.1),
                  ),
                  Text(
                    dest.country,
                    style: TextStyle(
                      color: Colors.white.withValues(alpha: 0.7),
                      fontSize: 13,
                      letterSpacing: 1.5,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const SizedBox(height: 8),
                  Text(dest.tagline, style: TextStyle(color: Colors.white.withValues(alpha: 0.55), fontSize: 12)),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

class _Destination {
  final String city;
  final String country;
  final String emoji;
  final String tagline;
  final List<Color> gradient;
  const _Destination({
    required this.city,
    required this.country,
    required this.emoji,
    required this.tagline,
    required this.gradient,
  });
}

// ─────────────────────────────────────────────────────────────
// 4. Profile Cards
// ─────────────────────────────────────────────────────────────

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

  static const _profiles = [
    _Profile(
      name: 'Alex Kim',
      role: 'Product Designer',
      initials: 'AK',
      tags: ['Figma', 'Motion', 'iOS'],
      avatarColor: Color(0xFF6200EA),
      bgColor: Color(0xFFF3E5F5),
    ),
    _Profile(
      name: 'Sara Chen',
      role: 'Backend Engineer',
      initials: 'SC',
      tags: ['Go', 'Postgres', 'k8s'],
      avatarColor: Color(0xFF00695C),
      bgColor: Color(0xFFE0F2F1),
    ),
    _Profile(
      name: 'Jordan Lee',
      role: 'ML Researcher',
      initials: 'JL',
      tags: ['PyTorch', 'LLMs', 'RLHF'],
      avatarColor: Color(0xFFBF360C),
      bgColor: Color(0xFFFBE9E7),
    ),
    _Profile(
      name: 'Maya Patel',
      role: 'iOS Developer',
      initials: 'MP',
      tags: ['Swift', 'SwiftUI', 'Flutter'],
      avatarColor: Color(0xFF0D47A1),
      bgColor: Color(0xFFE3F2FD),
    ),
    _Profile(
      name: 'Luca Rossi',
      role: 'DevOps Engineer',
      initials: 'LR',
      tags: ['AWS', 'Terraform', 'CI/CD'],
      avatarColor: Color(0xFF37474F),
      bgColor: Color(0xFFECEFF1),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFF8F8F8),
      appBar: AppBar(
        title: const Text('Profile Cards'),
        backgroundColor: const Color(0xFF880E4F),
        foregroundColor: Colors.white,
      ),
      body: SwiperDemoBody(
        itemCount: _profiles.length,
        builder: (onCardChange) => FlipCardSwiper(
          onCardChange: onCardChange,
          cardData: _profiles,
          animationDuration: const Duration(milliseconds: 600),
          cardEdgeThickness: 0,
          cardBuilder: (context, index, visibleIndex) {
            if (index < 0 || index >= _profiles.length) {
              return const SizedBox.shrink();
            }
            final p = _profiles[index];
            return Container(
              key: ValueKey(index),
              width: 310,
              height: 195,
              decoration: BoxDecoration(
                color: p.bgColor,
                borderRadius: BorderRadius.circular(20),
                boxShadow: [
                  BoxShadow(color: Colors.black.withValues(alpha: 0.07), blurRadius: 20, offset: const Offset(0, 6)),
                ],
              ),
              padding: const EdgeInsets.all(22),
              child: Row(
                children: [
                  // Avatar
                  Container(
                    width: 64,
                    height: 64,
                    decoration: BoxDecoration(color: p.avatarColor, shape: BoxShape.circle),
                    alignment: Alignment.center,
                    child: Text(
                      p.initials,
                      style: const TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.w700),
                    ),
                  ),
                  const SizedBox(width: 18),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          p.name,
                          style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w800, color: Color(0xFF1A1A1A)),
                        ),
                        const SizedBox(height: 3),
                        Text(
                          p.role,
                          style: TextStyle(fontSize: 12, color: Colors.grey.shade600, fontWeight: FontWeight.w500),
                        ),
                        const SizedBox(height: 14),
                        Wrap(
                          spacing: 6,
                          runSpacing: 6,
                          children: p.tags
                              .map(
                                (t) => Container(
                                  padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
                                  decoration: BoxDecoration(
                                    color: p.avatarColor.withValues(alpha: 0.12),
                                    borderRadius: BorderRadius.circular(20),
                                  ),
                                  child: Text(
                                    t,
                                    style: TextStyle(fontSize: 10, color: p.avatarColor, fontWeight: FontWeight.w700),
                                  ),
                                ),
                              )
                              .toList(),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

class _Profile {
  final String name;
  final String role;
  final String initials;
  final List<String> tags;
  final Color avatarColor;
  final Color bgColor;
  const _Profile({
    required this.name,
    required this.role,
    required this.initials,
    required this.tags,
    required this.avatarColor,
    required this.bgColor,
  });
}

// ─────────────────────────────────────────────────────────────
// 5. Minimal News
// ─────────────────────────────────────────────────────────────

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

  static const _articles = [
    _Article(
      tag: 'TECHNOLOGY',
      headline: 'Flutter 4.0 ships\nImpeller on all platforms',
      source: 'Flutter Blog',
      time: '2 min read',
      tagColor: Color(0xFF1565C0),
    ),
    _Article(
      tag: 'SCIENCE',
      headline: "Webb telescope spots\nwater on exoplanet's surface",
      source: 'Nature',
      time: '4 min read',
      tagColor: Color(0xFF2E7D32),
    ),
    _Article(
      tag: 'DESIGN',
      headline: 'Why spatial computing\nchanges everything',
      source: 'UX Collective',
      time: '3 min read',
      tagColor: Color(0xFF6A1B9A),
    ),
    _Article(
      tag: 'BUSINESS',
      headline: 'Open source models\novertake proprietary rivals',
      source: 'The Information',
      time: '5 min read',
      tagColor: Color(0xFFBF360C),
    ),
    _Article(
      tag: 'HEALTH',
      headline: 'Sleep and productivity:\nwhat new data shows',
      source: 'Harvard Health',
      time: '3 min read',
      tagColor: Color(0xFF00695C),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFFFFFFF),
      appBar: AppBar(
        title: const Text('Minimal News'),
        backgroundColor: const Color(0xFF212121),
        foregroundColor: Colors.white,
      ),
      body: SwiperDemoBody(
        itemCount: _articles.length,
        builder: (onCardChange) => FlipCardSwiper(
          onCardChange: onCardChange,
          cardData: _articles,
          animationDuration: const Duration(milliseconds: 400),
          downDragDuration: const Duration(milliseconds: 120),
          completionPhaseScale: 0.9,
          perspectiveDepth: 0.001,
          cardEdgeThickness: 0,
          cardBorderRadius: BorderRadius.circular(12),
          cardBuilder: (context, index, visibleIndex) {
            if (index < 0 || index >= _articles.length) {
              return const SizedBox.shrink();
            }
            final a = _articles[index];
            return Container(
              key: ValueKey(index),
              width: 320,
              height: 180,
              decoration: BoxDecoration(
                color: const Color(0xFFFAFAFA),
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: const Color(0xFFE0E0E0)),
                boxShadow: [
                  BoxShadow(color: Colors.black.withValues(alpha: 0.04), blurRadius: 12, offset: const Offset(0, 4)),
                ],
              ),
              padding: const EdgeInsets.all(22),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
                    decoration: BoxDecoration(color: a.tagColor, borderRadius: BorderRadius.circular(4)),
                    child: Text(
                      a.tag,
                      style: const TextStyle(
                        color: Colors.white,
                        fontSize: 9,
                        fontWeight: FontWeight.w800,
                        letterSpacing: 1.2,
                      ),
                    ),
                  ),
                  const SizedBox(height: 12),
                  Text(
                    a.headline,
                    style: const TextStyle(
                      fontSize: 17,
                      fontWeight: FontWeight.w700,
                      height: 1.35,
                      color: Color(0xFF111111),
                    ),
                  ),
                  const Spacer(),
                  Row(
                    children: [
                      Text(
                        a.source,
                        style: TextStyle(fontSize: 11, color: Colors.grey.shade500, fontWeight: FontWeight.w600),
                      ),
                      const SizedBox(width: 8),
                      Container(
                        width: 3,
                        height: 3,
                        decoration: BoxDecoration(color: Colors.grey.shade400, shape: BoxShape.circle),
                      ),
                      const SizedBox(width: 8),
                      Text(a.time, style: TextStyle(fontSize: 11, color: Colors.grey.shade500)),
                    ],
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

class _Article {
  final String tag;
  final String headline;
  final String source;
  final String time;
  final Color tagColor;
  const _Article({
    required this.tag,
    required this.headline,
    required this.source,
    required this.time,
    required this.tagColor,
  });
}
77
likes
160
points
972
downloads
screenshot

Documentation

API reference

Publisher

verified publisherhashstudios.dev

Weekly Downloads

A customizable, swipeable card widget with smooth flip animations and haptic support.

Repository (GitHub)
View/report issues

Topics

#animation #card #swipe #transform #flip

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flip_card_swiper