Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Implement Flutter animations: implicit, explicit, hero, staggered, and physics-based with a decision tree guide.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/staggered.md
1# Staggered Animations Reference23Staggered animations run multiple animations with different timing offsets, creating sequential or overlapping visual effects.45## Core Concept67All animations share one `AnimationController`. Each animation has an `Interval` defining when it starts and ends within the controller's timeline.89## Basic Staggered Animation1011### Two-Property Stagger1213```dart14class StaggeredFadeSlide extends StatelessWidget {15const StaggeredFadeSlide({super.key, required this.controller});1617final AnimationController controller;1819// Fade in first (0.0 - 0.5 of controller)20late final Animation<double> opacity = Tween<double>(begin: 0, end: 1).animate(21CurvedAnimation(22parent: controller,23curve: const Interval(0.0, 0.5, curve: Curves.easeIn),24),25);2627// Slide in later (0.25 - 1.0 of controller)28late final Animation<Offset> slide = Tween<Offset>(29begin: Offset(0, 0.5),30end: Offset.zero,31).animate(32CurvedAnimation(33parent: controller,34curve: const Interval(0.25, 1.0, curve: Curves.easeOut),35),36);3738@override39Widget build(BuildContext context) {40return AnimatedBuilder(41animation: controller,42builder: (context, child) {43return FadeTransition(44opacity: opacity,45child: SlideTransition(46position: slide,47child: child,48),49);50},51child: const Text('Staggered animation'),52);53}54}55```5657### Multiple Intervals Example5859```dart60class MultiPropertyStagger extends StatelessWidget {61const MultiPropertyStagger({super.key, required this.controller});6263final AnimationController controller;6465// Opacity: 0.0 - 0.1 (10%)66late final Animation<double> opacity = Tween<double>(begin: 0, end: 1).animate(67CurvedAnimation(68parent: controller,69curve: const Interval(0.0, 0.1, curve: Curves.ease),70),71);7273// Width: 0.125 - 0.25 (12.5% - 25%)74late final Animation<double> width = Tween<double>(begin: 50, end: 150).animate(75CurvedAnimation(76parent: controller,77curve: const Interval(0.125, 0.25, curve: Curves.ease),78),79);8081// Height: 0.25 - 0.375 (25% - 37.5%)82late final Animation<double> height = Tween<double>(begin: 50, end: 150).animate(83CurvedAnimation(84parent: controller,85curve: const Interval(0.25, 0.375, curve: Curves.ease),86),87);8889// Border radius: 0.375 - 0.5 (37.5% - 50%)90late final Animation<BorderRadius?> borderRadius = BorderRadiusTween(91begin: BorderRadius.circular(4),92end: BorderRadius.circular(75),93).animate(94CurvedAnimation(95parent: controller,96curve: const Interval(0.375, 0.5, curve: Curves.ease),97),98);99100// Color: 0.5 - 0.625 (50% - 62.5%)101late final Animation<Color?> color = ColorTween(102begin: Colors.red,103end: Colors.orange,104).animate(105CurvedAnimation(106parent: controller,107curve: const Interval(0.5, 0.625, curve: Curves.ease),108),109);110111@override112Widget build(BuildContext context) {113return AnimatedBuilder(114animation: controller,115builder: (context, child) {116return Opacity(117opacity: opacity.value,118child: Container(119width: width.value,120height: height.value,121decoration: BoxDecoration(122color: color.value,123borderRadius: borderRadius.value,124),125child: child,126),127);128},129child: const FlutterLogo(),130);131}132}133```134135## Controller Setup136137### Single Controller138139```dart140class _StaggerState extends State<StaggerWidget>141with SingleTickerProviderStateMixin {142late AnimationController _controller;143144@override145void initState() {146super.initState();147_controller = AnimationController(148duration: const Duration(milliseconds: 2000),149vsync: this,150);151_controller.forward();152}153154@override155void dispose() {156_controller.dispose();157super.dispose();158}159160@override161Widget build(BuildContext context) {162return StaggerAnimation(controller: _controller.view);163}164}165```166167### Multiple Controllers168169For independent animation sequences:170171```dart172class _ComplexStaggerState extends State<ComplexStaggerWidget>173with TickerProviderStateMixin {174late AnimationController _fadeInController;175late AnimationController _slideController;176177@override178void initState() {179super.initState();180181_fadeInController = AnimationController(182duration: const Duration(milliseconds: 500),183vsync: this,184);185186_slideController = AnimationController(187duration: const Duration(milliseconds: 500),188vsync: this,189);190191// Start fade, then slide192_fadeInController.forward().then((_) {193_slideController.forward();194});195}196197@override198void dispose() {199_fadeInController.dispose();200_slideController.dispose();201super.dispose();202}203}204```205206## Interval Timing207208### Understanding Interval209210```dart211Interval(2120.25, // Start at 25% of controller duration2130.75, // End at 75% of controller duration214curve: Curves.easeInOut,215)216```217218**Example:** If controller duration is 2000ms:219- Animation starts at 500ms (0.25 * 2000)220- Animation ends at 1500ms (0.75 * 2000)221- Animation takes 1000ms (1500 - 500)222223### Overlapping Intervals224225```dart226// Animation 1: 0.0 - 0.5 (first half)227anim1 = Tween<double>(begin: 0, end: 1).animate(228CurvedAnimation(229parent: controller,230curve: const Interval(0.0, 0.5),231),232);233234// Animation 2: 0.3 - 0.8 (starts before anim1 ends)235anim2 = Tween<double>(begin: 0, end: 1).animate(236CurvedAnimation(237parent: controller,238curve: const Interval(0.3, 0.8),239),240);241242// Animation 3: 0.6 - 1.0 (starts after anim1 ends)243anim3 = Tween<double>(begin: 0, end: 1).animate(244CurvedAnimation(245parent: controller,246curve: const Interval(0.6, 1.0),247),248);249```250251### Gaps Between Animations252253```dart254// Animation 1: 0.0 - 0.4255// Gap: 0.4 - 0.5256// Animation 2: 0.5 - 0.9257258anim1 = Tween<double>(begin: 0, end: 1).animate(259CurvedAnimation(260parent: controller,261curve: const Interval(0.0, 0.4),262),263);264265anim2 = Tween<double>(begin: 0, end: 1).animate(266CurvedAnimation(267parent: controller,268curve: const Interval(0.5, 0.9),269),270);271```272273## Staggered List Animation274275### Menu Items Animation276277```dart278class StaggeredMenu extends StatefulWidget {279const StaggeredMenu({super.key});280281static const _menuItems = [282'Home',283'Profile',284'Settings',285'About',286];287288@override289State<StaggeredMenu> createState() => _StaggeredMenuState();290}291292class _StaggeredMenuState extends State<StaggeredMenu>293with SingleTickerProviderStateMixin {294static const _itemDelayTime = Duration(milliseconds: 50);295static const _itemAnimationTime = Duration(milliseconds: 250);296297late AnimationController _controller;298late List<Animation<double>> _itemAnimations;299300@override301void initState() {302super.initState();303_controller = AnimationController(304duration: _itemAnimationTime +305(_itemDelayTime * (StaggeredMenu._menuItems.length - 1)),306vsync: this,307);308309_itemAnimations = StaggeredMenu._menuItems.map((item) {310final index = StaggeredMenu._menuItems.indexOf(item);311return Tween<double>(begin: 0, end: 1).animate(312CurvedAnimation(313parent: _controller,314curve: Interval(315index * 0.1, // Start later for each item316(index * 0.1) + 0.4, // Each takes 40% of controller317curve: Curves.easeOut,318),319),320);321}).toList();322323_controller.forward();324}325326@override327void dispose() {328_controller.dispose();329super.dispose();330}331332@override333Widget build(BuildContext context) {334return ListView.builder(335itemCount: StaggeredMenu._menuItems.length,336itemBuilder: (context, index) {337return AnimatedBuilder(338animation: _controller,339builder: (context, child) {340return SlideTransition(341position: Tween<Offset>(342begin: Offset(1.0, 0),343end: Offset.zero,344).animate(CurvedAnimation(345parent: _controller,346curve: Interval(347index * 0.1,348(index * 0.1) + 0.4,349curve: Curves.easeOut,350),351)),352child: FadeTransition(353opacity: _itemAnimations[index],354child: child,355),356);357},358child: ListTile(359title: Text(StaggeredMenu._menuItems[index]),360),361);362},363);364}365}366```367368### Grid Animation369370```dart371class StaggeredGrid extends StatefulWidget {372const StaggeredGrid({super.key, required this.itemCount});373374final int itemCount;375376@override377State<StaggeredGrid> createState() => _StaggeredGridState();378}379380class _StaggeredGridState extends State<StaggeredGrid>381with SingleTickerProviderStateMixin {382late AnimationController _controller;383late List<Animation<double>> _animations;384385@override386void initState() {387super.initState();388_controller = AnimationController(389duration: const Duration(milliseconds: 1500),390vsync: this,391);392393_animations = List.generate(widget.itemCount, (index) {394return Tween<double>(begin: 0, end: 1).animate(395CurvedAnimation(396parent: _controller,397curve: Interval(398index / widget.itemCount,399(index / widget.itemCount) + 0.3,400curve: Curves.easeOut,401),402),403);404});405406_controller.forward();407}408409@override410void dispose() {411_controller.dispose();412super.dispose();413}414415@override416Widget build(BuildContext context) {417return GridView.builder(418gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(419crossAxisCount: 2,420),421itemCount: widget.itemCount,422itemBuilder: (context, index) {423return AnimatedBuilder(424animation: _controller,425builder: (context, child) {426return FadeTransition(427opacity: _animations[index],428child: ScaleTransition(429scale: _animations[index],430child: child,431),432);433},434child: Card(435child: Center(child: Text('Item $index')),436),437);438},439);440}441}442```443444## Complex Staggered Patterns445446### Sequential Completion447448```dart449animation1 = Tween<double>(begin: 0, end: 1).animate(450CurvedAnimation(451parent: controller,452curve: const Interval(0.0, 0.3),453),454);455456animation2 = Tween<double>(begin: 0, end: 1).animate(457CurvedAnimation(458parent: controller,459curve: const Interval(0.3, 0.6),460),461);462463animation3 = Tween<double>(begin: 0, end: 1).animate(464CurvedAnimation(465parent: controller,466curve: const Interval(0.6, 1.0),467),468);469```470471### Ripple Effect472473```dart474// Center expands first, then outward475for (int i = 0; i < itemCount; i++) {476final distance = (i - centerIndex).abs();477animations[i] = Tween<double>(begin: 0, end: 1).animate(478CurvedAnimation(479parent: controller,480curve: Interval(481distance * 0.1,482(distance * 0.1) + 0.3,483curve: Curves.easeOut,484),485),486);487}488```489490### Staggered Reveal491492```dart493// Reveal items one by one from top494for (int i = 0; i < itemCount; i++) {495animations[i] = CurvedAnimation(496parent: controller,497curve: Interval(498i * 0.1,499(i * 0.1) + 0.15,500curve: Curves.easeOut,501),502);503}504505// Use for opacity or transform506Opacity(opacity: animations[i].value)507```508509## Duration Calculation510511### Calculate Total Duration512513```dart514const itemDelay = Duration(milliseconds: 50);515const itemAnimationTime = Duration(milliseconds: 250);516const itemCount = 10;517518final totalDuration = itemAnimationTime +519(itemDelay * (itemCount - 1));520521// 250ms + (50ms * 9) = 700ms522```523524### Using in Controller525526```dart527_controller = AnimationController(528duration: totalDuration,529vsync: this,530);531```532533### Dynamic Duration534535```dart536late AnimationController _controller;537late Duration _totalDuration;538539@override540void initState() {541super.initState();542543final itemCount = _items.length;544const itemDelay = Duration(milliseconds: 50);545const itemAnimationTime = Duration(milliseconds: 250);546547_totalDuration = itemAnimationTime + (itemDelay * (itemCount - 1));548549_controller = AnimationController(550duration: _totalDuration,551vsync: this,552);553554// Create animations...555_controller.forward();556}557```558559## Repeating Staggered Animations560561### Loop on Completion562563```dart564_controller.addStatusListener((status) {565if (status == AnimationStatus.completed) {566_controller.reset();567_controller.forward();568}569});570```571572### Ping-Pong (Forward and Reverse)573574```dart575_controller.addStatusListener((status) {576if (status == AnimationStatus.completed) {577_controller.reverse();578} else if (status == AnimationStatus.dismissed) {579_controller.forward();580}581});582```583584## Debugging Staggered Animations585586### Slow Animation587588```dart589void main() {590timeDilation = 10.0; // 10x slower591runApp(MyApp());592}593```594595### Print Interval Ranges596597```dart598for (int i = 0; i < _animations.length; i++) {599final anim = _animations[i] as CurvedAnimation;600print('Animation $i: ${anim.curve}');601}602603// Output:604// Animation 0: Interval(0.0, 0.4, Curves.easeOut)605// Animation 1: Interval(0.1, 0.5, Curves.easeOut)606// ...607```608609### Visualize Animation State610611```dart612class DebugStaggeredWidget extends StatelessWidget {613const DebugStaggeredWidget({super.key, required this.controller, required this.animations});614615final AnimationController controller;616final List<Animation<double>> animations;617618@override619Widget build(BuildContext context) {620return Column(621children: [622for (int i = 0; i < animations.length; i++)623Text('Animation $i: ${animations[i].value.toStringAsFixed(2)}'),624],625);626}627}628```629630## Performance Best Practices631632### DO633634- Use one controller when animations are related635- Calculate total duration correctly636- Use `AnimatedBuilder` for optimal rebuilds637- Profile with Flutter DevTools638- Test on various devices639- Consider reducing animation complexity on low-end devices640641### DON'T642643- Create too many controllers unnecessarily644- Forget to dispose controllers645- Use complex widget trees inside staggered animations646- Animate too many items simultaneously (jank)647- Use very long durations without good reason648649## Common Patterns650651### Loading Animation652653```dart654// Dot 1: 0.0 - 0.3655// Dot 2: 0.1 - 0.4656// Dot 3: 0.2 - 0.5657// Repeat forever658```659660### Success Animation Sequence661662```dart663// Checkmark appears: 0.0 - 0.3664// "Success!" text fades in: 0.2 - 0.5665// Confetti falls: 0.3 - 1.0666```667668### Onboarding Steps669670```dart671// Step 1: 0.0 - 0.25672// Step 2: 0.25 - 0.5673// Step 3: 0.5 - 0.75674// Step 4: 0.75 - 1.0675```676677## Accessibility678679- Respect `MediaQuery.disableAnimations` setting680- Provide alternative to complex staggered animations681- Ensure content remains accessible during animation682- Test with screen readers683684## Advanced Techniques685686### Conditional Staggering687688```dart689// Show animations based on device performance690final isLowEnd = Platform.isAndroid && deviceInfo.version.sdkInt < 21;691692final staggerDelay = isLowEnd693? Duration(milliseconds: 100) // Slower on low-end694: Duration(milliseconds: 50);695```696697### Adaptive Staggering698699```dart700// Adjust based on screen size701final screenWidth = MediaQuery.of(context).size.width;702final itemsPerRow = screenWidth ~/ 150;703704// Calculate stagger based on grid position705final row = index ~/ itemsPerRow;706final col = index % itemsPerRow;707final startDelay = (row * 0.2) + (col * 0.05);708```709710### Physics-Influenced Staggering711712```dart713// Use spring physics for staggered elements714for (int i = 0; i < itemCount; i++) {715animations[i] = Tween<double>(begin: 0, end: 1).animate(716CurvedAnimation(717parent: controller,718curve: Interval(719i * 0.05,720(i * 0.05) + 0.5,721curve: Curves.elasticOut,722),723),724);725}726```727