diff --git a/.cspell.yaml b/.cspell.yaml index 3211d4dd..6747f45c 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -89,10 +89,13 @@ words: - shellenv - shorebirdtech - softwareupdate + - subclassing - subosito - tabler - temurin + - tweens - uiscene + - vsync - vvago - webkitallowfullscreen - widgetbook diff --git a/src/content/docs/flutter-concepts/animations.mdx b/src/content/docs/flutter-concepts/animations.mdx new file mode 100644 index 00000000..a167e18d --- /dev/null +++ b/src/content/docs/flutter-concepts/animations.mdx @@ -0,0 +1,194 @@ +--- +title: 'Flutter Animations' +description: + Reference guide for implementing implicit, explicit, and physics-based + animations in Flutter. +sidebar: + order: 6 +--- + +The Flutter animation framework provides a layered architecture for controlling +motion. At its core, the framework categorizes animations into implicit +transitions, explicit controller-driven animations, and physics-based +simulations. + +## Implicit animations + +Implicit animations automatically interpolate between a current value and a new +target value over a specified duration. The framework calculates intermediate +frames when a property changes and the widget rebuilds. + +`AnimatedContainer` is a versatile implicit widget that can animate changes to +size, color, padding, and alignment simultaneously: + +```dart +AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + width: isExpanded ? 200 : 100, + decoration: BoxDecoration( + color: isExpanded ? Colors.blue : Colors.red, + borderRadius: BorderRadius.circular(isExpanded ? 16.0 : 8.0), + ), + child: const Text('Hello'), +) +``` + +The underlying architecture relies on `ImplicitlyAnimatedWidget`. When the +framework detects a change in the widget's configuration (via +`didUpdateWidget`), it creates an internal `AnimationController` and begins a +transition from the old values to the new values. + +### Non-linear interpolation (curves) + +By default, animations transition linearly. Applying a `Curve` modifies the +interpolation rate. The framework provides standard curves via the `Curves` +class, such as `Curves.easeIn`, `Curves.bounceOut`, and `Curves.elasticInOut`. +You can also define custom mathematical curves by subclassing `Curve` and +overriding the `transformInternal` method. + +## Explicit animations + +Explicit animations require manual lifecycle management using an +`AnimationController`. This approach is necessary for looping animations, +sequenced transitions, and animations triggered by gesture events. + +```mermaid +graph TD + T["TickerProvider
(vsync)"] -->|Tick per frame| C["AnimationController
(0.0 to 1.0)"] + C -->|Linear Value| A["CurvedAnimation
(Non-linear timing)"] + A -->|Eased Value| TW["Tween
(Maps to target data type)"] + TW -->|Output Value| W["AnimatedBuilder
(Rebuilds UI)"] + + style T fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px,color:#000 + style C fill:#e8f5e9,stroke:#4caf50,stroke-width:2px,color:#000 + style A fill:#fff3e0,stroke:#ff9800,stroke-width:2px,color:#000 + style TW fill:#fce4ec,stroke:#e91e63,stroke-width:2px,color:#000 + style W fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px,color:#000 +``` + +### Animation controllers and vsync + +An `AnimationController` generates a new value whenever the hardware is ready +for a new frame. To prevent off-screen computations and synchronize with the +device refresh rate, the controller requires a `TickerProvider` (passed via the +`vsync` argument). + +```dart +class _MyWidgetState extends State with SingleTickerProviderStateMixin { + late final AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + ); + } + + @override + void dispose() { + _controller.dispose(); // Prevent memory leaks + super.dispose(); + } +} +``` + +### Tweens and mapping + +A controller typically outputs values between `0.0` and `1.0`. A `Tween` maps +this unit interval to a required data type (such as `Color`, `Offset`, or +`double`). + +```dart +final Animation sizeAnimation = Tween( + begin: 10.0, + end: 100.0, +).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeOut, + ), +); +``` + +### Rebuilding the UI + +To apply the animation to the render tree, use an `AnimatedBuilder` or subclass +`AnimatedWidget`. This localized rebuilding prevents the entire widget tree from +refreshing on every frame. + +```dart +AnimatedBuilder( + animation: sizeAnimation, + builder: (BuildContext context, Widget? child) { + return Container( + width: sizeAnimation.value, + height: sizeAnimation.value, + color: Colors.blue, + child: child, // The child is passed through to prevent rebuilding it + ); + }, + child: const FlutterLogo(), +) +``` + +## Physics-based simulations + +For animations that must react naturally to user input (such as a scroll view +snapping into place), use the physics simulation engine rather than +fixed-duration controllers. + +The `AnimationController` can execute a `Simulation` using +`controller.animateWith(Simulation)`. The framework provides several standard +physics models: + +- `SpringSimulation`: Models tension and friction. +- `GravitySimulation`: Models gravitational acceleration. +- `FrictionSimulation`: Models gradual deceleration. + +```dart +final SpringDescription spring = SpringDescription( + mass: 1.0, + stiffness: 100.0, + damping: 10.0, +); + +final SpringSimulation simulation = SpringSimulation( + spring, + 0.0, // start position + 100.0, // end position + 0.0, // initial velocity +); + +_controller.animateWith(simulation); +``` + +## Third-party vector animations + +For complex vector graphics and state-machine animations, use third-party +formats rather than the built-in widget framework: + +- **[Lottie](https://lottiefiles.com/)**: Uses animations exported as JSON from + Adobe After Effects. Best for static, non-interactive visual assets. +- **[Rive](https://rive.app/)**: Uses interactive state machines designed for + real-time manipulation. Best for dynamic assets requiring programmatic state + changes. + +## Performance considerations + +Modifying properties that affect layout (such as `width`, `height`, or +`padding`) forces the framework to recalculate geometry on every frame, which +can cause dropped frames. + +To optimize performance, animate properties that are composited directly on the +GPU without altering layout constraints: + +- **Opacity**: Use `FadeTransition` or `AnimatedOpacity`. +- **Transform**: Use `RotationTransition`, `ScaleTransition`, or + `SlideTransition`. + +When using `AnimatedBuilder`, always pass static child widgets via the `child` +parameter. This prevents the framework from recreating the child subtree during +every animation tick.