Rémi Rousselet
Rémi Rousselet

Reputation: 277067

Why does AnimationController needs a vsync?

When using AnimationController, what is the purpose of that vsync parameter?

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this, // Why do we need this?
    );
  }

  // ...
}

Upvotes: 7

Views: 2282

Answers (1)

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 277067

AnimationController's vsync parameter has one purpose: Controlling the progress of the animation based on external factors.

There are typically three main usages:

  • devtools like "slow animations", which reduce the speed of AnimationControllers by 50%.
  • widget testing. By using vsync, this allows tests to skip frames to target a very specific state of the animation. This is both precise and doesn't involve waiting for real-time.
  • it allows animations to be "muted" when the widget associated with the SingleTickerProviderStateMixin is not visible anymore

The last scenario is the main reason why our widgets need to that SingleTickerProviderStateMixin. Knowing what widget is associated with the animation matters. We can't just use a TickerProvider obtained from the root widget of our application.

Through that vsync, this will avoid scenarios where our widget is no longer visible (for example if another route is pushed on the top of it), but the animation is still playing and therefore makes our screen to keep refreshing

A way of seeing that behavior is by using the "performance overlay" devtool combined with widgets like CircularProgressIndicator, which internally uses AnimationController.

If we use Opacity to hide our indicator (which doesn't pause animations):

Opacity(
  opacity: 0,
  child: CircularProgressIndicator(),
)

Then the performance overlay shows that our screen keeps refreshing:

opacity performance overlay

Now, if we add a TickerMode (implicitly done by widgets like Visibility and Navigator), we can pause the animation, which stops the unnecessary refresh:

Opacity(
  opacity: 0,
  child: TickerMode(
    enabled: false,
    child: CircularProgressIndicator(),
  ),
),

muted

Upvotes: 12

Related Questions