Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
AnimationController в Flutter: Полное руководство
AnimationController — это основной инструмент для управления анимациями в Flutter. Это не просто воспроизводит анимацию, а управляет временем, значениями и слушателями. Расскажу как это работает и как правильно его использовать.
1. Что такое AnimationController?
AnimationController — это объект, который:
- Управляет временем анимации (от 0 до duration)
- Генерирует значения от 0.0 до 1.0
- Уведомляет слушателей об изменениях
- Может повторять, реверсировать, останавливать
final controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this, // требует TickerProvider (обычно State)
);
// После создания нужно запустить
controller.forward(); // Начать от 0 к 1
// Значение контроллера
print(controller.value); // 0.0 в начале, 1.0 в конце
2. Создание и инициализация
class AnimationScreen extends StatefulWidget {
@override
State<AnimationScreen> createState() => _AnimationScreenState();
}
class _AnimationScreenState extends State<AnimationScreen>
with SingleTickerProviderStateMixin { // ← Важно!
late AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this, // TickerProvider
);
// Слушаем изменения
controller.addListener(() {
setState(() {}); // Перестроить при каждом обновлении
});
// Или слушаем завершение
controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
print('Анимация завершена');
}
});
}
@override
void dispose() {
controller.dispose(); // Критично!
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (controller.isAnimating) {
controller.stop();
} else if (controller.isCompleted) {
controller.reverse();
} else {
controller.forward();
}
},
child: Container(
width: 100 * controller.value, // Использовать значение
height: 100,
color: Colors.blue,
),
);
}
}
3. Основные методы
final controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
// Воспроизведение анимации
controller.forward(); // От 0 к 1
await controller.forward(); // С ожиданием завершения
controller.reverse(); // От 1 к 0
await controller.reverse();
controller.repeat(); // Повторять бесконечно
controller.repeat(reverse: true); // Туда-сюда
controller.stop(); // Остановить
controller.reset(); // Вернуть к 0
controller.animateTo(0.5); // К произвольному значению
await controller.animateTo(0.5);
// Проверка статуса
print(controller.value); // Текущее значение (0.0 - 1.0)
print(controller.isAnimating); // Сейчас ли анимирует
print(controller.isCompleted); // Завершена ли
print(controller.isDismissed); // Находится ли в начале
4. Слушатели (Listeners)
controller.addListener(() {
// Вызывается при каждом изменении значения
// Много раз в секунду (60 раз при 60 FPS)
print('Value: ${controller.value}');
});
controller.addStatusListener((status) {
// Вызывается при изменении статуса
// Может быть: dismissed, forward, reverse, completed
switch (status) {
case AnimationStatus.dismissed:
print('Анимация в начале');
case AnimationStatus.forward:
print('Проигрывается вперёд');
case AnimationStatus.reverse:
print('Проигрывается в обратном');
case AnimationStatus.completed:
print('Анимация завершена');
}
});
// Удалить слушателя
void removeListener(() {
print('Старый слушатель');
});
controller.removeListener(removeListener);
5. Анимирование значений
Простой способ: умножить на значение
class SimpleAnimation extends StatefulWidget {
@override
State<SimpleAnimation> createState() => _SimpleAnimationState();
}
class _SimpleAnimationState extends State<SimpleAnimation>
with SingleTickerProviderStateMixin {
late AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
controller.addListener(() => setState(() {}));
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => controller.forward(from: 0.0),
child: Transform.scale(
scale: 0.5 + (controller.value * 0.5), // От 0.5 до 1.0
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
Правильный способ: Animation widget
class ProperAnimation extends StatefulWidget {
@override
State<ProperAnimation> createState() => _ProperAnimationState();
}
class _ProperAnimationState extends State<ProperAnimation>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> scaleAnimation;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
// Создаём Animation, которая преобразует значение
scaleAnimation = Tween<double>(
begin: 0.5,
end: 1.0,
).animate(
CurvedAnimation(parent: controller, curve: Curves.easeOut),
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => controller.forward(from: 0.0),
child: AnimatedBuilder(
animation: scaleAnimation,
builder: (context, child) => Transform.scale(
scale: scaleAnimation.value,
child: child,
),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
6. Tween для преобразования значений
// Tween преобразует значение от 0-1 в диапазон begin-end
final colorAnimation = ColorTween(
begin: Colors.red,
end: Colors.blue,
).animate(controller);
final sizeAnimation = Tween<double>(
begin: 10.0,
end: 100.0,
).animate(controller);
final offsetAnimation = Tween<Offset>(
begin: Offset.zero,
end: Offset(200, 0),
).animate(controller);
// Использование
Container(
width: sizeAnimation.value,
height: sizeAnimation.value,
color: colorAnimation.value,
)
7. CurvedAnimation - кривые анимации
// Без кривой - линейно
var linearAnimation = controller;
// С кривой - более естественно
var curvedAnimation = CurvedAnimation(
parent: controller,
curve: Curves.easeOut, // Начало быстрое, конец медленное
);
var animation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(curvedAnimation);
// Доступные кривые:
// Curves.linear - равномерно
// Curves.ease - ease-in-out
// Curves.easeIn - медленно начинает
// Curves.easeOut - медленно заканчивает
// Curves.easeInOut - медленно везде
// Curves.elasticIn - пружинка в начале
// Curves.elasticOut - пружинка в конце
// Curves.bounceOut - подпрыгивание
8. AnimatedBuilder - оптимизация
// ❌ Перестраивает весь виджет
class BadAnimation extends StatefulWidget {
@override
State<BadAnimation> createState() => _BadAnimationState();
}
class _BadAnimationState extends State<BadAnimation>
with SingleTickerProviderStateMixin {
late AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
controller.addListener(() => setState(() {})); // setState!
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ExpensiveWidget(), // Перестраивается каждый раз!
Transform.scale(
scale: 0.5 + (controller.value * 0.5),
child: Container(),
),
],
);
}
}
// ✅ Правильно: только AnimatedBuilder перестраивается
class GoodAnimation extends StatefulWidget {
@override
State<GoodAnimation> createState() => _GoodAnimationState();
}
class _GoodAnimationState extends State<GoodAnimation>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> scaleAnimation;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
scaleAnimation = Tween<double>(
begin: 0.5,
end: 1.0,
).animate(controller);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ExpensiveWidget(), // НЕ перестраивается
AnimatedBuilder(
animation: scaleAnimation,
builder: (context, child) => Transform.scale(
scale: scaleAnimation.value,
child: child,
),
child: Container(),
),
],
);
}
}
9. Множественные анимации
class MultipleAnimations extends StatefulWidget {
@override
State<MultipleAnimations> createState() => _MultipleAnimationsState();
}
class _MultipleAnimationsState extends State<MultipleAnimations>
with TickerProviderStateMixin { // ← Важно: для многих анимаций
late AnimationController scaleController;
late AnimationController rotateController;
late AnimationController opacityController;
@override
void initState() {
super.initState();
scaleController = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
rotateController = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
opacityController = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
scaleController.forward(from: 0.0);
rotateController.repeat(); // Бесконечно
opacityController.forward(from: 0.0);
},
child: AnimatedBuilder(
animation: Listenable.merge([
scaleController,
rotateController,
opacityController,
]),
builder: (context, child) => Transform.scale(
scale: 0.5 + (scaleController.value * 0.5),
child: Transform.rotate(
angle: rotateController.value * 2 * 3.14159,
child: Opacity(
opacity: opacityController.value,
child: child,
),
),
),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
);
}
@override
void dispose() {
scaleController.dispose();
rotateController.dispose();
opacityController.dispose();
super.dispose();
}
}
10. Практический пример: Loading Spinner
class LoadingSpinner extends StatefulWidget {
@override
State<LoadingSpinner> createState() => _LoadingSpinnerState();
}
class _LoadingSpinnerState extends State<LoadingSpinner>
with SingleTickerProviderStateMixin {
late AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
)..repeat(); // Повторять бесконечно
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (context, child) => Transform.rotate(
angle: controller.value * 2 * 3.14159,
child: child,
),
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue,
width: 3,
),
borderRadius: BorderRadius.circular(25),
),
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
11. Важные практики
✅ ДО:
// 1. Всегда вызови dispose()
@override
void dispose() {
controller.dispose();
super.dispose();
}
// 2. Используй AnimatedBuilder для оптимизации
AnimatedBuilder(animation: controller, ...)
// 3. Используй CurvedAnimation для естественности
CurvedAnimation(parent: controller, curve: Curves.easeOut)
// 4. Используй TickerProviderStateMixin
with SingleTickerProviderStateMixin { ... }
❌ ИЗБЕГАЙ:
// Забыть dispose()
controller.dispose(); // ← Обязательно!
// setState в listener
controller.addListener(() => setState(() {})); // Медленно
// Множественные контроллеры без TickerProvider
with SingleTickerProviderStateMixin { // Не хватит!
Вывод
AnimationController:
- Управляет временем анимации
- Генерирует значения от 0 к 1
- Требует TickerProvider (State mixin)
- Всегда вызови dispose()
- Используй AnimatedBuilder для оптимизации
- Используй Tween и CurvedAnimation для преобразований
Это основной инструмент для всех анимаций в Flutter!