import 'package:flutter/material.dart'; /// Retains all tab states and scroll positions, with no page-swipe recognizer. class TabDeck extends StatefulWidget { const TabDeck({super.key, required this.index, required this.children}); final int index; final List children; @override State createState() => _TabDeckState(); } class _TabDeckState extends State with SingleTickerProviderStateMixin { late final AnimationController _motion = AnimationController( vsync: this, duration: const Duration(milliseconds: 330), value: 1, ); int? _outgoing; int _direction = 1; @override void didUpdateWidget(TabDeck oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.index != widget.index) { _outgoing = oldWidget.index; _direction = widget.index > oldWidget.index ? 1 : -1; if (MediaQuery.disableAnimationsOf(context)) { _motion.value = 1; } else { _motion.forward(from: 0); } } } @override void dispose() { _motion.dispose(); super.dispose(); } @override Widget build(BuildContext context) => AnimatedBuilder( animation: _motion, builder: (context, _) { final t = Curves.easeOutCubic.transform(_motion.value); return Stack( fit: StackFit.expand, children: [ for (var i = 0; i < widget.children.length; i++) Offstage( offstage: i != widget.index && (i != _outgoing || _motion.isCompleted), child: TickerMode( enabled: i == widget.index, child: IgnorePointer( ignoring: i != widget.index, child: ExcludeSemantics( excluding: i != widget.index, child: Opacity( opacity: i == widget.index ? t : (i == _outgoing ? 1 - t : 0), child: Transform.translate( offset: Offset( (i == widget.index ? 1 - t : -t) * 22 * _direction, 0, ), child: widget.children[i], ), ), ), ), ), ), ], ); }, ); }