Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,13 @@ words:
- shellenv
- shorebirdtech
- softwareupdate
- subclassing
- subosito
- tabler
- temurin
- tweens
- uiscene
- vsync
- vvago
- webkitallowfullscreen
- widgetbook
Expand Down
194 changes: 194 additions & 0 deletions src/content/docs/flutter-concepts/animations.mdx
Original file line number Diff line number Diff line change
@@ -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<br>(vsync)"] -->|Tick per frame| C["AnimationController<br>(0.0 to 1.0)"]
C -->|Linear Value| A["CurvedAnimation<br>(Non-linear timing)"]
A -->|Eased Value| TW["Tween<br>(Maps to target data type)"]
TW -->|Output Value| W["AnimatedBuilder<br>(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<MyWidget> 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<double> sizeAnimation = Tween<double>(
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.
Loading