Irbisa · Cheatsheet
Animations & Motion
AnimationController, Ticker, Tween, Curves, AnimatedBuilder, staggered motion, Lottie and Rive
What are
Animation,AnimationController,TweenandTickerin Flutter, and how do they work together?class _PopInState extends State<PopIn> with SingleTickerProviderStateMixin { // vsync: this -> the mixin creates the Ticker that drives the controller late final AnimationController _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 400), )..forward();…How does an implicit animation widget like
AnimatedContainerorAnimatedOpacityknow what to animate?class _ToggleCardState extends State<ToggleCard> { bool _open = false; @override Widget build(BuildContext context) { return GestureDetector(…What is wrong with this
State, and why does its widget test fail with "was disposed with an active Ticker"?class _FaderState extends State<Fader> with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { final controller = AnimationController( vsync: this, duration: widget.duration, )..forward(); return FadeTransition(opacity: controller, child: widget.child); }}Why does an
AnimationControllerneedvsync, and when do you needTickerProviderStateMixininstead ofSingleTickerProviderStateMixin?What does this code print, and why does
stop()not change the controller's status?final c = AnimationController( vsync: this, duration: const Duration(milliseconds: 400),)..addStatusListener((s) => debugPrint(s.name)); c.forward();await Future<void>.delayed(const Duration(milliseconds: 200));c.stop();debugPrint('${c.status.name} animating=${c.isAnimating}');c.reverse();Find the bug: the like heart pops on the first tap, but every later tap toggles the icon without any animation.
class _LikeState extends State<Like> with SingleTickerProviderStateMixin { late final _pop = AnimationController( // disposed in dispose() vsync: this, duration: const Duration(milliseconds: 300)); late final _scale = _pop.drive(TweenSequence([ TweenSequenceItem(tween: Tween(begin: 1.0, end: 1.3), weight: 1), TweenSequenceItem(tween: Tween(begin: 1.3, end: 1.0), weight: 1), ])); @override Widget build(BuildContext context) => IconButton( onPressed: () { widget.onToggle(); _pop.forward(); }, icon: ScaleTransition(scale: _scale, child: Icon(widget.icon)), );}How does a
Tweenturn a controller's 0..1 into aColor, anOffsetor aTextStyle, and what doestween.animate(controller)return?class _ProfileCardState extends State<ProfileCard> with SingleTickerProviderStateMixin { late final _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 350))..forward(); // Offset has + - *, so the base Tween works…Why does
Curves.easeOutBackbreak anOpacityanimation, and how do you keep the overshoot where it is allowed?late final _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 420)); // Scale may overshoot: easeOutBack peaks above 1.0late final _pop = CurvedAnimation( parent: _c,…How do you write a custom animation curve, and what must it return at
t = 0andt = 1?// A designer's cubic-bezier(0.2, 0, 0, 1): no subclass neededconst emphasized = Cubic(0.2, 0.0, 0.0, 1.0); // CSS steps(n): jumps in n equal steps, e.g. a ticking clock handclass StepsCurve extends Curve { const StepsCurve(this.steps) : assert(steps > 0);…When do you use
AnimatedBuilder, a customAnimatedWidget, or a built-in*Transitionwidget to show an explicit animation?// One-off effect: AnimatedBuilder, with the static subtree passed as childAnimatedBuilder( animation: _controller, child: const ProductImage(), // built once, not on every tick builder: (context, child) => Transform( alignment: Alignment.center,…You need to pop a screen exactly when its exit animation ends:
await controller.reverse()or a status listener, and what if the user interrupts it?// wrong: after an interruption this never returns, so callers awaiting close() hang and finally blocks never runFuture<void> closeNaive() async { await _c.reverse(); Navigator.of(context).pop();}…Find the bug: after the third wrong PIN, a single shake logs the
pin_error_shownanalytics event three times.class _PinFieldState extends State<PinField> with SingleTickerProviderStateMixin { late final _shake = AnimationController(vsync: this, duration: Durations.medium4); void onWrongPin() { _shake.addStatusListener((s) { if (s.isCompleted) widget.onShaken(); }); _shake.forward(from: 0); } @override Widget build(BuildContext context) => AnimatedBuilder( animation: _shake, builder: (_, child) => Transform.translate( offset: Offset(sin(_shake.value * pi * 6) * 12, 0), child: child), child: widget.child, );} // dispose() disposes _shakeA card fades in, then its title slides up, then the button pops: one controller with
Intervals or three controllers, and how do you keep them in sync?// One clock for the whole sequence: 300 + 300 + 300 mslate final _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 900))..forward(); // Each slice is a FRACTION of that durationlate final Animation<double> _cardFade = _c.drive(…A progress badge animates toward a value its parent passes down, and a new value lands mid-flight: what does
TweenAnimationBuilderdo with your tween?class UploadBadge extends StatelessWidget { const UploadBadge({super.key, required this.progress}); final double progress; // the parent may pass a new target at any time @override…Your
AnimatedSwitcherswaps the text but never animates: how does it decide the child is new, and how do you fix it?// wrong: same type, no key -> Widget.canUpdate says "same widget", no animationAnimatedSwitcher( duration: const Duration(milliseconds: 250), child: Text('$_count'),);…Why does
AnimatedList.removeItemneed a builder for a row you already removed from your data, and what must happen in the same step?class _InboxState extends State<Inbox> { final _listKey = GlobalKey<AnimatedListState>(); final List<Mail> _mails = List.of(seedMails); Widget _row(Mail mail, Animation<double> animation) => SizeTransition( sizeFactor: animation,…A designer hands you a four-second celebration animation: when do you choose Lottie, Rive or a hand-built Flutter animation, and how do you play it from code?
late final _c = AnimationController(vsync: this); Lottie.asset( 'assets/celebrate.json', controller: _c, onLoaded: (composition) {…A card grows when a details section opens and its height is unknown until layout runs: how do you animate a size you cannot write a
Tweenfor?// The height comes out of layout, so animate the box, not a numberAnimatedSize( duration: const Duration(milliseconds: 220), curve: Curves.easeOut, alignment: Alignment.topCenter, child: Column(…How do you build your own implicit
AnimatedXwidget: what doesImplicitlyAnimatedWidgetmake you implement, and what is the contract offorEachTween?class AnimatedRing extends ImplicitlyAnimatedWidget { const AnimatedRing({ super.key, required this.color, required this.thickness, required super.duration,…A bottom sheet has to follow the user's thumb and finish the motion on its own when they let go: how do you wire an
AnimationControllerto a drag?class _SheetState extends State<Sheet> with SingleTickerProviderStateMixin { static const double _extent = 320; // sheet height in logical pixels // 0 = hidden, 1 = open late final _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 300));…The card snaps back with
easeOutand testers say the motion feels dead: when does a physics simulation beat any curve you could pick?// unbounded: an underdamped spring passes its target,// and a 0..1 controller would clamp that overshoot awaylate final _c = AnimationController.unbounded(vsync: this); static final _spring = SpringDescription.withDampingRatio( mass: 1,…