← Назад к вопросам
Что такое SingleTickerProviderStateMixin и TickerProviderStateMixin?
1.7 Middle🔥 122 комментариев
#Flutter виджеты#Анимации
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
SingleTickerProviderStateMixin и TickerProviderStateMixin
Это миксины для эффективного управления анимациями во Flutter. Они связывают жизненный цикл State с частотой обновления экрана (Ticker).
Что такое Ticker?
Ticker — это объект, который синхронизирует анимацию с вертикальной развёрткой экрана.
┌─────────────────────────────────────────┐
│ Frame 0: Ticker срабатывает │
│ ├─ elapsedTime = 0ms │
│ ├─ AnimationController обновляет value │
│ └─ UI перестраивается │
└─────────────────────────────────────────┘
↓ (16ms на 60fps)
┌─────────────────────────────────────────┐
│ Frame 1: Ticker срабатывает │
│ ├─ elapsedTime = 16ms │
│ ├─ AnimationController обновляет value │
│ └─ UI перестраивается │
└─────────────────────────────────────────┘
Тикер жёстко связан с экраном (60fps или 120fps на некоторых устройствах).
SingleTickerProviderStateMixin — одна анимация
Используется когда в State одна анимация.
// ❌ Плохо — создаёт AnimationController без Ticker
class BadAnimationState extends State<BadAnimation> {
late AnimationController controller;
@override
void initState() {
super.initState();
// ОШИБКА: нет vsync (TickerProvider)
controller = AnimationController(duration: Duration(seconds: 1));
}
}
// ✅ Хорошо — используем SingleTickerProviderStateMixin
class GoodAnimationState extends State<GoodAnimation>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
@override
void initState() {
super.initState();
// Создаём controller с vsync = this (TickerProvider)
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this, // Синхронизация с экраном
);
// Создаём анимацию
animation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
controller.dispose(); // ВАЖНО: очистить
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.scale(
scale: animation.value,
child: Container(
width: 200,
height: 200,
color: Colors.blue,
),
);
},
);
}
}
TickerProviderStateMixin — несколько анимаций
Используется когда в State несколько анимаций.
class MultipleAnimationsState extends State<MultipleAnimations>
with TickerProviderStateMixin {
late AnimationController scaleController;
late AnimationController rotationController;
late AnimationController opacityController;
@override
void initState() {
super.initState();
// Несколько AnimationControllers с одним TickerProvider
scaleController = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
rotationController = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
opacityController = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
// Запустить все анимации
scaleController.repeat(reverse: true);
rotationController.repeat();
opacityController.repeat(reverse: true);
}
@override
void dispose() {
scaleController.dispose();
rotationController.dispose();
opacityController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: Listenable.merge([
scaleController,
rotationController,
opacityController,
]),
builder: (context, child) {
return Opacity(
opacity: opacityController.value,
child: Transform.scale(
scale: 1 + scaleController.value * 0.5,
child: Transform.rotate(
angle: rotationController.value * 2 * pi,
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
),
),
);
},
),
);
}
}
Практический пример: Анимированная кнопка
class AnimatedButtonState extends State<AnimatedButton>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> scaleAnimation;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
scaleAnimation = Tween<double>(begin: 1.0, end: 0.9).animate(
CurvedAnimation(parent: controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
void _onPressed() {
controller.forward().then((_) {
controller.reverse();
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => _onPressed(),
child: ScaleTransition(
scale: scaleAnimation,
child: ElevatedButton(
onPressed: () {},
child: Text('Press me'),
),
),
);
}
}
Сравнение двух миксинов
// SingleTickerProviderStateMixin — простой случай
class OneAnimation extends State<...> with SingleTickerProviderStateMixin {
late AnimationController controller; // Одна анимация
@override
void initState() {
super.initState();
controller = AnimationController(vsync: this);
}
}
// TickerProviderStateMixin — сложные случаи
class MultipleAnimations extends State<...> with TickerProviderStateMixin {
late AnimationController controller1; // Первая анимация
late AnimationController controller2; // Вторая анимация
late AnimationController controller3; // Третья анимация
@override
void initState() {
super.initState();
controller1 = AnimationController(vsync: this);
controller2 = AnimationController(vsync: this);
controller3 = AnimationController(vsync: this);
}
}
Почему это важно?
Без Ticker (без vsync):
// ❌ НЕПРАВИЛЬНО
controller = AnimationController(duration: Duration(seconds: 1));
// Анимация работает, но не синхронизирована с экраном
// Может происходить "tearing" (разрывание изображения)
// Более высокое потребление батареи
С Ticker (с vsync):
// ✅ ПРАВИЛЬНО
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
// Идеально синхронизирована с вертикальной разверткой
// Плавная анимация
// Оптимальное потребление батареи
Практический пример: Фрагмент экрана с TabController
class TabScreenState extends State<TabScreen>
with SingleTickerProviderStateMixin {
late TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(
length: 3,
vsync: this, // Синхронизируем табы с экраном
);
}
@override
void dispose() {
tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: tabController,
tabs: [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
),
),
body: TabBarView(
controller: tabController,
children: [
Center(child: Text('Content 1')),
Center(child: Text('Content 2')),
Center(child: Text('Content 3')),
],
),
);
}
}
Таблица миксинов
| Миксин | Анимаций | Использование | Производительность |
|---|---|---|---|
| SingleTickerProviderStateMixin | Одна | Простые анимации | Лучше |
| TickerProviderStateMixin | Много | Сложные UI | Хорошо |
| Без миксина | - | Без анимаций | Не применимо |
Совет для production
Для очень сложных анимаций с множеством контроллеров:
class ComplexAnimationState extends State<...>
with TickerProviderStateMixin {
final List<AnimationController> controllers = [];
AnimationController createController({required Duration duration}) {
final controller = AnimationController(duration: duration, vsync: this);
controllers.add(controller);
return controller;
}
@override
void dispose() {
for (var controller in controllers) {
controller.dispose();
}
super.dispose();
}
}