Во 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.