Урок по треку Flutter и Dart 6 мин

Анимации: неявные, явные, Hero

Неявные и явные анимации, AnimationController, Ticker, Hero.

Во Flutter анимация это значение, которое меняется от кадра к кадру, и перестроение, которое это значение использует. Весь вопрос в том, кто управляет значением: фреймворк сам или вы через контроллер.

Неявные анимации

Виджеты AnimatedContainer, AnimatedOpacity, AnimatedAlign, AnimatedSwitcher сами анимируют переход, когда меняется их параметр:

AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  curve: Curves.easeOut,
  width: expanded ? 240 : 80,
  color: expanded ? Colors.indigo : Colors.grey,
)

Поменяли expanded через setState, и контейнер плавно перешёл к новым значениям. Своё значение анимируют через TweenAnimationBuilder. Неявные анимации подходят для переходов «из состояния А в состояние Б» и не требуют контроллера.

Явные анимации

Когда нужно управлять временем: повторять, запускать в обратную сторону, связывать с жестом, ставить на паузу, нужен AnimationController.

class _PulseState extends State<Pulse> with SingleTickerProviderStateMixin {
  late final AnimationController _c = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 1),
  )..repeat(reverse: true);
  late final Animation<double> _scale =
      Tween(begin: 0.9, end: 1.1).animate(CurvedAnimation(parent: _c, curve: Curves.easeInOut));

  @override
  void dispose() {
    _c.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) =>
      ScaleTransition(scale: _scale, child: widget.child);
}
  • Контроллер выдаёт значение от 0 до 1 за duration. Методы: forward, reverse, repeat, animateTo, stop.
  • vsync это TickerProvider. Ticker вызывает колбэк на каждом кадре и не тикает, когда виджет не виден (например, экран перекрыт другим маршрутом): батарея не тратится. Отсюда миксины SingleTickerProviderStateMixin (один контроллер) и TickerProviderStateMixin (несколько).
  • Tween переводит 0..1 в нужный диапазон (размеры, цвета через ColorTween), CurvedAnimation задаёт кривую.
  • Забытый dispose у контроллера даёт ошибку «Ticker was not disposed» и утечку.

Как перерисовывать дёшево

Плохо: _c.addListener(() => setState(() {})). Тогда весь build экрана выполняется 60-120 раз в секунду.

Хорошо: готовые *Transition (FadeTransition, SlideTransition, RotationTransition) или AnimatedBuilder с параметром child. child строится один раз, а на каждом кадре пересчитывается только обёртка. FadeTransition вообще меняет прозрачность на уровне слоя, без перестроения и раскладки.

AnimatedBuilder(
  animation: _c,
  child: const ExpensiveCard(),      // создаётся один раз
  builder: (context, child) => Transform.rotate(angle: _c.value * 6.28, child: child),
)

Hero и переходы между экранами

Hero с одинаковым tag на двух экранах перелетает с одного на другой при навигации: картинка товара из списка увеличивается в карточку. Теги на одном экране должны быть уникальны, иначе будет ошибка. Свои переходы между маршрутами задаются через PageRouteBuilder с transitionsBuilder или pageTransitionsTheme в теме.

Физика и сложные анимации

AnimationController.animateWith(SpringSimulation(...)) даёт пружинную анимацию, которая естественно продолжает жест. Для анимаций от дизайнеров используют Lottie или Rive: они проигрывают готовые файлы.

Что спросят на собесе

  • Неявные и явные анимации: когда что.
  • Что такое vsync, Ticker и зачем миксин.
  • Роли AnimationController, Tween, CurvedAnimation.
  • Как анимировать без перестроения всего экрана, зачем child в AnimatedBuilder.
  • Как работает Hero.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.