flutter_card_stack 1.0.0
flutter_card_stack: ^1.0.0 copied to clipboard
A production-ready animated card stack carousel widget for Flutter with smooth parallax animations, depth effects, and cross-platform support for mobile, web, and desktop.
import 'package:flutter/material.dart';
import 'package:flutter_card_stack/flutter_card_stack.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Card Stack Example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
scaffoldBackgroundColor: const Color(0xFF2d3447),
),
home: const CardStackExample(),
);
}
}
class CardStackExample extends StatefulWidget {
const CardStackExample({Key? key}) : super(key: key);
@override
State<CardStackExample> createState() => _CardStackExampleState();
}
class _CardStackExampleState extends State<CardStackExample> {
late PageController _pageController;
late double _currentPage;
final bool infiniteScroll = false;
/// SOURCE DATA
final List<CardItem> _cardItems = [
CardItem(
imageUrl: 'https://picsum.photos/400/600?random=1',
title: 'Mountain Adventure',
),
CardItem(
imageUrl: 'https://picsum.photos/400/600?random=2',
title: 'Ocean Dreams',
),
CardItem(
imageUrl: 'https://picsum.photos/400/600?random=3',
title: 'Forest Escape',
),
CardItem(
imageUrl: 'https://picsum.photos/400/600?random=4',
title: 'Desert Journey',
),
];
/// REQUIRED
List<String> get _images =>
_cardItems.map((e) => e.imageUrl).toList();
List<String> get _titles =>
_cardItems.map((e) => e.title).toList();
/// SAFE: length == cards, null hides button
List<String?> get _buttonText => [
'Explore',
'Discover', // button hidden
null, // button hidden
];
/// SAFE card taps
List<VoidCallback?> get _onCardTap => List.generate(
_cardItems.length,
(i) => () => debugPrint('Card $i tapped'),
);
/// SAFE button taps
List<VoidCallback?> get _onButtonTap => List.generate(
_cardItems.length,
(i) => () => debugPrint('Button $i tapped'),
);
int get _initialPage => infiniteScroll ? 1000 : 0;
@override
void initState() {
super.initState();
_currentPage = 0;
_pageController = PageController(initialPage: _initialPage);
_pageController.addListener(_onPageChanged);
}
void _onPageChanged() {
setState(() {
if (infiniteScroll) {
final page = _pageController.page ?? _currentPage;
_currentPage = page % _cardItems.length;
} else {
_currentPage = _pageController.page ?? _currentPage;
}
});
}
@override
void dispose() {
_pageController.removeListener(_onPageChanged);
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Card Stack Demo',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
backgroundColor: const Color(0xFF2d3447),
elevation: 0,
centerTitle: true,
),
body: SafeArea(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
children: [
const SizedBox(height: 20),
/// ================= CARD STACK =================
Stack(
children: [
Positioned.fill(
child: IgnorePointer(
child: PageView.builder(
controller: _pageController,
itemCount:
infiniteScroll ? null : _cardItems.length,
itemBuilder: (_, __) => const SizedBox(),
),
),
),
GestureDetector(
behavior: HitTestBehavior.translucent,
onHorizontalDragUpdate: (details) {
_pageController.position.jumpTo(
_pageController.position.pixels -
(details.primaryDelta ?? 0),
);
},
onHorizontalDragEnd: (details) {
final velocity = details.primaryVelocity ?? 0;
final page = _pageController.page ?? 0;
int targetPage;
if (velocity < -500) {
targetPage = page.ceil();
} else if (velocity > 500) {
targetPage = page.floor();
} else {
targetPage = page.round();
}
targetPage =
targetPage.clamp(0, _cardItems.length - 1);
_pageController.animateToPage(
targetPage,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
},
child: FlutterCardStack(
_currentPage,
images: _images,
title: _titles,
buttonText: _buttonText,
onCardTap: _onCardTap,
onButtonTap: _onButtonTap,
),
),
],
),
/// =================================================
const SizedBox(height: 40),
],
),
),
),
);
}
}