← Назад к вопросам

Что такое 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();
  }
}
Что такое SingleTickerProviderStateMixin и TickerProviderStateMixin? | PrepBro