card_deck_view
English | 简体中文
A Flutter card deck with continuous stack animation, horizontal swiping and spring return. Build cards from any widget, control navigation programmatically, and display large lists through a small mounted window.

Desktop preview · Mobile preview · Example guide
Features
- Rear cards move forward as you drag; subtle rotation gives the stack depth.
- Distance and release velocity determine whether a card advances or springs back.
- Both swipe directions advance; controller navigation also supports previous, jump and reset.
- Configure visible layers, spacing, rotation, shadows and physics.
- Touch, mouse, trackpad, horizontal wheel, keyboard and accessibility actions.
- Pure Flutter, with no swiper or native plugin dependency.
Getting started
Requires Flutter 3.44.0+ and Dart 3.12.2+ (<4.0.0).
After the first release is published, install from pub.dev with flutter pub add card_deck_view, or add:
dependencies:
flutter:
sdk: flutter
card_deck_view: ^0.0.1
Run flutter pub get. To develop from this checkout before publication, use a local path dependency; the bundled example already uses path: ../. Only import the public library:
import 'package:card_deck_view/card_deck_view.dart';
Minimal example
import 'package:card_deck_view/card_deck_view.dart';
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: DeckExample()));
class DeckExample extends StatelessWidget {
const DeckExample({super.key});
@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: SizedBox(
width: 300,
height: 400,
child: CardDeckView<String>(
items: const ['One', 'Two', 'Three', 'Four'],
itemBuilder: (context, item, index) => ColoredBox(
color: Colors.white,
child: Center(child: Text(item)),
),
),
),
),
);
}
Give the deck finite width and height, using a SizedBox, AspectRatio within bounded constraints, or another constrained parent. Leave space around it for rear cards, shadows and swipes. Card content and aspect ratio belong to your application.
Widget API
| Property | Default | Purpose |
|---|---|---|
items |
Required | List<T> containing the card data |
itemBuilder |
Required | (BuildContext, T, int) → Widget; index refers to the source list |
visibleCount |
3 |
Positive number of visible layers; mounts at most this number plus one buffer |
loop |
true |
Wrap to the beginning after the last card |
initialIndex |
0 |
Non-negative start index, clamped to available items |
controller |
null |
Optional CardDeckController |
itemKeyBuilder |
null |
(T) → Object returning a unique stable identity; index identity otherwise |
style |
CardDeckStyle() |
Stack appearance |
physics |
CardDeckPhysics() |
Swipe and return behavior |
onSwipe |
null |
(int, DeckSwipeDirection) → void; removed index and physical exit direction |
onChanged |
null |
(int) → void; current index after navigation or data reconciliation |
onCompleted |
null |
Called once each time a non-looping deck becomes exhausted |
emptyBuilder |
null |
Empty/exhausted state; a blank widget by default |
DeckSwipeDirection contains left and right. Both advance to the next item. onSwipe fires after an exit animation completes, before onChanged; initialization does not emit onChanged. Previous, jump and reset do not emit onSwipe.
Controller
Create the controller in your widget's State, pass it to CardDeckView(controller: controller, ...), and dispose it with that State:
final controller = CardDeckController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
| Method | Behavior |
|---|---|
next() / swipeLeft() |
Exit left and advance |
swipeRight() |
Exit right and advance |
previous() |
Bring the previous card back from the left; wrap only when looping |
moveTo(index) |
Cancel motion and immediately position at a valid index |
reset() |
Cancel motion and return to initialIndex, clamped to current data |
Read currentIndex, isBusy and isAttached. The controller extends ChangeNotifier, so ListenableBuilder can observe changes. isBusy includes dragging and animations. New navigation commands are ignored during animations; users can catch a returning card by dragging again. Jump and reset cancel motion immediately.
Commands while detached do nothing. With a deck attached, an invalid moveTo index throws RangeError. One controller can bind to only one deck; a disposed controller cannot be reused. Binding and data updates synchronize values without notifying listeners during build; use onChanged for index changes caused by data updates.
Style and physics
Pass these objects through the widget's style and physics properties:
const style = CardDeckStyle(
spacing: 12,
scaleStep: 0.04,
rotationStep: -0.015,
dragRotation: 0.08,
maxRotation: 0.08,
borderRadius: 24,
);
const physics = CardDeckPhysics(
swipeThreshold: 0.28,
velocityThreshold: 700,
spring: SpringDescription(mass: 1, stiffness: 300, damping: 30),
swipeDuration: Duration(milliseconds: 280),
swipeCurve: Curves.easeOutCubic,
);
These are the defaults. Import package:flutter/material.dart alongside the package for SpringDescription and Curves.
spacingis the vertical distance between layers;scaleStepis the scale reduction per layer. Deep stacks clamp scale to a positive minimum.- Angles use radians.
rotationStepaffects rear layers;dragRotationmaps horizontal displacement to top-card rotation;maxRotationcaps it. shadowsaccepts a list ofBoxShadow; useconst []for none. The default is a subtle shadow.swipeThresholdis a fraction of card width, in(0, 1].velocityThresholduses logical pixels per second. Fast releases use velocity direction; slower releases use displacement.- The spring drives return motion. Duration and curve drive exits and previous-card entry. Exit distance accounts for the viewport width.
Lists, empty states and images
- The deck mounts at most
visibleCount + 1cards and never repeats an item inside that window. Animation ticks update transforms without rebuilding card content. - Supply a new list when data changes. With
itemKeyBuilder, the active item follows reordering; if removed, the index is clamped. Data changes cancel motion. Stable identities must be unique. - Cards remaining in the window retain their State; off-window cards are disposed. Store persistent business state outside individual card widgets.
- An empty list shows
emptyBuilder. A single looping item can be dragged and returned but does not advance. - With
loop: false, removing the last item enters the empty state.currentIndexandonChangedthen equalitems.length—check the range before indexing your list.previous()restores the last card;reset()restores the starting position.
Images are supplied through itemBuilder; the package does not download or cache them. The default example uses twelve local transparent animal illustrations, so it works offline. Its custom AnimalCard combines an image, handwritten text and a reset button. You can supply any Widget through itemBuilder. The optional PhotoCard recipe demonstrates network loading, errors, retry and decode sizing without changing the global image cache.
Input and accessibility
Horizontal touch/mouse drags and trackpad pans move the deck. Vertical gestures remain available to a parent scroll view. Horizontal wheel events accumulate until 120 ms of inactivity before release; vertical wheel events are ignored.
Focus the deck with Tab or a drag, then use left/right keys to exit in that direction. Accessibility exposes the current card number and increase/decrease actions; rear card semantics are excluded. System reduced-motion settings skip automatic travel and rebound while retaining direct drag feedback.
Examples
The surrounding interface uses monochrome text navigation and lightweight controls. The animal cards and their motion stay independent of the application chrome.
cd example
flutter pub get
flutter run -d chrome
# or: flutter run -d windows
| Page | What it demonstrates |
|---|---|
| Animal cards | 12 transparent illustrations, handwritten captions and navigation |
| Playground | Compact settings panel; collapsible on narrow screens |
| 1,000 cards | A large list reusing 12 local illustrations, with a live mounted-card count |
Playground preview · 1,000-card preview
The shared toolbar provides Previous and Next. The More actions menu contains Reset, Jump and About the artwork. Each card also has a reset button. White rounded cards, soft shadows and handwritten captions are built entirely in the example. See artwork and font provenance.
Platforms and development
The example includes Android, iOS, Web, Windows, macOS and Linux runners. Windows/Web builds and Android debug APK compilation have been verified; Web interaction and Windows startup have been smoke-tested. Android device runtime and iOS/macOS/Linux builds require additional verification on the corresponding devices/hosts. See the example guide for details and manual checks.
flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze
flutter test
Implementation lives in lib/src/; consumers should use lib/card_deck_view.dart. The package has no CI configuration or automatic publication workflow.
License
Package source code is available under the MIT License. Example illustrations are not relicensed under MIT; see artwork and font notices.
Libraries
- card_deck_view
- Physics-driven, interactive card stacks for Flutter.