card_deck_view 0.0.1
card_deck_view: ^0.0.1 copied to clipboard
A smooth, physics-driven card deck with continuous stack interpolation, controllers, and cross-platform gestures.
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.