Files
TPSchedule/mobile/lib/widgets/tab_deck.dart
T

79 lines
2.3 KiB
Dart

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<Widget> children;
@override
State<TabDeck> createState() => _TabDeckState();
}
class _TabDeckState extends State<TabDeck> 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],
),
),
),
),
),
),
],
);
},
);
}