Ahmet Balaman LogoAhmet Balaman

Flutter: Explicit Animations ile Tam Kontrol - AnimationController Rehberi

personAhmet Balaman
calendar_today
FlutterAnimationAnimationControllerExplicit AnimationTween

Implicit animasyonlar harika ama bazen daha fazla kontrole ihtiyacınız var. Animasyonu istediğiniz zaman başlatmak, durdurmak, geri sarmak veya hızını değiştirmek istiyorsanız, Explicit Animations (belirgin animasyonlar) devreye giriyor.

AnimationController, Flutter'ın animasyon motorunun kalbi. Her kare için değer üreten, istediğiniz gibi yönlendirebileceğiniz bir orkestra şefi gibi.

Implicit vs Explicit: Hangisi Ne Zaman?

Hızlı bir hatırlatma:

Özellik Implicit Explicit
Kullanım setState ile otomatik Manuel kontrol
Kod Miktarı Az (3-5 satır) Fazla (15-30 satır)
Kontrol Sınırlı Tam kontrol
Başlat/Durdur Otomatik Manuel
Kullanım Alanı Basit geçişler Karmaşık animasyonlar
Örnek AnimatedContainer AnimationController

Ne Zaman Explicit Kullanmalısınız?

  • Animasyonu butona basınca başlatmak istiyorsanız
  • Animasyonu sonsuz döngüde çalıştırmak istiyorsanız
  • Birden fazla animasyonu senkronize etmek istiyorsanız
  • Kullanıcı input'una göre animasyonu kontrol etmek istiyorsanız (örn: kaydırma mesafesine göre)
  • Animasyonu geri sarmak istiyorsanız

AnimationController Temelleri

AnimationController, 0.0'dan 1.0'a (veya istediğiniz aralığa) doğru sürekli artan bir değer üretir.

Temel Kullanım

class BasicAnimationDemo extends StatefulWidget {
  @override
  _BasicAnimationDemoState createState() => _BasicAnimationDemoState();
}

class _BasicAnimationDemoState extends State<BasicAnimationDemo> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this, // TickerProvider gerekli
    );
  }

  @override
  void dispose() {
    _controller.dispose(); // Hafıza sızıntısını önlemek için!
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return Transform.rotate(
              angle: _controller.value * 2 * 3.14159, // 360 derece
              child: child,
            );
          },
          child: Icon(Icons.star, size: 100, color: Colors.amber),
        ),
        SizedBox(height: 40),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => _controller.forward(),
              child: Text('Başlat'),
            ),
            SizedBox(width: 10),
            ElevatedButton(
              onPressed: () => _controller.reverse(),
              child: Text('Geri Sar'),
            ),
            SizedBox(width: 10),
            ElevatedButton(
              onPressed: () => _controller.reset(),
              child: Text('Sıfırla'),
            ),
          ],
        ),
      ],
    );
  }
}

Canlı Demo

Aşağıdaki interaktif örnekte explicit animation'ları deneyebilirsiniz:

💡 Yukarıdaki örnek yüklenmiyorsa, yeni sekmede çalıştırmak için DartPad linkine tıklayın.

TickerProvider Nedir?

vsync parametresi zorunludur. Peki bu TickerProvider ne işe yarar?

Neden Gerekli?

Flutter, her saniye 60 kare (60 FPS) çizmeye çalışır. AnimationController, her kare için yeni bir değer üretir. Ancak widget ekranda görünmüyorsa (örneğin başka sayfaya geçtiniz), animasyon gereksiz yere CPU harcar.

vsync, widget ekranda olmadığında animasyonu otomatik olarak durdurur. Böylece:

  • Batarya tasarrufu
  • CPU tasarrufu
  • Performans artışı

Hangi Mixin'i Kullanmalısınız?

// Tek animasyon varsa
class _MyWidgetState extends State<MyWidget> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
}

// Birden fazla animasyon varsa
class _MyWidgetState extends State<MyWidget> 
    with TickerProviderStateMixin {
  late AnimationController _controller1;
  late AnimationController _controller2;
  late AnimationController _controller3;
}

AnimationController Metodları

AnimationController bir VCR (video kaset oynatıcı) gibi düşünün:

1. forward() - Oynat

Animasyonu baştan sona çalıştırır (0.0 → 1.0):

_controller.forward();

// Belirli bir değerden başlat
_controller.forward(from: 0.5); // 0.5'ten başla

2. reverse() - Geri Sar

Animasyonu sondan başa çalıştırır (1.0 → 0.0):

_controller.reverse();

// Belirli bir değerden geri sar
_controller.reverse(from: 0.8);

3. reset() - Sıfırla

Animasyonu 0.0'a döndürür (animasyon çalışmaz):

_controller.reset();

4. stop() - Durdur

Animasyonu mevcut değerde durdurur:

_controller.stop();

5. repeat() - Sonsuz Döngü

Animasyonu sürekli tekrarlar:

// Sonsuz döngü (0 → 1 → 0 → 1 ...)
_controller.repeat();

// Geri dönüşlü (0 → 1 → 0 → 1 ...)
_controller.repeat(reverse: true);

// Belirli sayıda tekrar
_controller.repeat(reverse: true, period: Duration(seconds: 1));

6. animateTo() - Belirli Değere Git

Mevcut değerden belirli bir değere animasyon yapar:

_controller.animateTo(0.7); // Mevcut değerden 0.7'ye git

// Custom duration ile
_controller.animateTo(
  0.5,
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
);

7. animateBack() - Geri Git

Mevcut değerden belirli bir değere geri animasyon yapar:

_controller.animateBack(0.3);

Tween: Değer Dönüşümü

AnimationController sadece 0.0 - 1.0 arası değer üretir. Peki 0 - 360 (derece) veya 50 - 200 (piksel) arasında değer istersek?

Tween (between kısaltması) değerleri dönüştürür.

Temel Tween Kullanımı

class TweenDemo extends StatefulWidget {
  @override
  _TweenDemoState createState() => _TweenDemoState();
}

class _TweenDemoState extends State<TweenDemo> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _sizeAnimation;
  late Animation<Color?> _colorAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );

    // Boyut animasyonu: 50 → 200
    _sizeAnimation = Tween<double>(
      begin: 50,
      end: 200,
    ).animate(_controller);

    // Renk animasyonu: Kırmızı → Mavi
    _colorAnimation = ColorTween(
      begin: Colors.red,
      end: Colors.blue,
    ).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return Container(
              width: _sizeAnimation.value,
              height: _sizeAnimation.value,
              decoration: BoxDecoration(
                color: _colorAnimation.value,
                borderRadius: BorderRadius.circular(20),
              ),
            );
          },
        ),
        SizedBox(height: 40),
        ElevatedButton(
          onPressed: () {
            if (_controller.status == AnimationStatus.completed) {
              _controller.reverse();
            } else {
              _controller.forward();
            }
          },
          child: Text('Animasyonu Çalıştır'),
        ),
      ],
    );
  }
}

Yaygın Tween Türleri

// Sayı (double)
Tween<double>(begin: 0, end: 100)

// Sayı (int)
IntTween(begin: 0, end: 255)

// Renk
ColorTween(begin: Colors.red, end: Colors.blue)

// Offset (konum)
Tween<Offset>(begin: Offset.zero, end: Offset(1.0, 0.0))

// Size (boyut)
Tween<Size>(begin: Size(50, 50), end: Size(200, 200))

// BorderRadius
Tween<BorderRadius>(
  begin: BorderRadius.circular(0),
  end: BorderRadius.circular(50),
)

// TextStyle
TextStyleTween(
  begin: TextStyle(fontSize: 14),
  end: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
)

CurvedAnimation: Animasyona Karakter Katmak

Tween değerleri dönüştürür, CurvedAnimation ise animasyonun karakterini belirler:

_controller = AnimationController(
  duration: Duration(seconds: 2),
  vsync: this,
);

// Curve uygulama
final curvedAnimation = CurvedAnimation(
  parent: _controller,
  curve: Curves.elasticOut, // Elastik efekt
);

// Tween ile birlikte kullanım
_sizeAnimation = Tween<double>(
  begin: 50,
  end: 200,
).animate(curvedAnimation);

Farklı Curve'ler

// Yumuşak giriş-çıkış
Curves.easeInOut

// Elastik (yay gibi)
Curves.elasticOut

// Zıplama
Curves.bounceOut

// Aşırı gitme (overshoot)
Curves.anticipate

// Hızlanma
Curves.accelerate

// Yavaşlama
Curves.decelerate

AnimatedBuilder: Performans Dostu Rebuild

AnimatedBuilder, sadece gerekli widget'ı yeniden oluşturur:

AnimatedBuilder(
  animation: _controller,
  builder: (context, child) {
    // Bu kısım her frame'de rebuild edilir
    return Transform.rotate(
      angle: _controller.value * 2 * 3.14159,
      child: child, // Bu rebuild edilmez!
    );
  },
  child: Icon(Icons.star, size: 100), // Sabit, cache'lenir
)

Performans İpucu: child parametresi, animasyon boyunca değişmeyen widget'lar için kullanılır. Flutter bunu cache'ler ve her frame'de yeniden oluşturmaz.

Animation Status: Durumu Dinleme

Animasyonun durumunu takip edebilirsiniz:

@override
void initState() {
  super.initState();
  _controller = AnimationController(
    duration: Duration(seconds: 2),
    vsync: this,
  );

  _controller.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
      print('Animasyon tamamlandı!');
      _controller.reverse(); // Otomatik geri sar
    } else if (status == AnimationStatus.dismissed) {
      print('Animasyon başa döndü!');
    }
  });
}

Status Türleri

AnimationStatus.forward    // İleri gidiyor (0 → 1)
AnimationStatus.reverse    // Geri gidiyor (1 → 0)
AnimationStatus.completed  // Tamamlandı (1.0'da)
AnimationStatus.dismissed  // Başa döndü (0.0'da)

Gerçek Dünya Örnekleri

1. Sonsuz Dönme (Loading Spinner)

class LoadingSpinner extends StatefulWidget {
  @override
  _LoadingSpinnerState createState() => _LoadingSpinnerState();
}

class _LoadingSpinnerState extends State<LoadingSpinner> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    )..repeat(); // Sonsuz döngü
  }

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Transform.rotate(
            angle: _controller.value * 2 * 3.14159,
            child: child,
          );
        },
        child: Icon(Icons.refresh, size: 50, color: Colors.blue),
      ),
    );
  }
}

2. Kalp Atışı Animasyonu (Pulsing Heart)

class PulsingHeart extends StatefulWidget {
  @override
  _PulsingHeartState createState() => _PulsingHeartState();
}

class _PulsingHeartState extends State<PulsingHeart> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 800),
      vsync: this,
    )..repeat(reverse: true); // Gidip gel

    _scaleAnimation = Tween<double>(
      begin: 0.8,
      end: 1.2,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
  }

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: _scaleAnimation,
        builder: (context, child) {
          return Transform.scale(
            scale: _scaleAnimation.value,
            child: child,
          );
        },
        child: Icon(Icons.favorite, size: 100, color: Colors.red),
      ),
    );
  }
}

3. Fade In ve Slide Up Kombinasyonu

class FadeSlideIn extends StatefulWidget {
  final Widget child;

  FadeSlideIn({required this.child});

  @override
  _FadeSlideInState createState() => _FadeSlideInState();
}

class _FadeSlideInState extends State<FadeSlideIn> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _opacityAnimation;
  late Animation<Offset> _slideAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 600),
      vsync: this,
    );

    _opacityAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeOut,
    ));

    _slideAnimation = Tween<Offset>(
      begin: Offset(0, 0.3), // Aşağıdan
      end: Offset.zero,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeOut,
    ));

    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return FadeTransition(
          opacity: _opacityAnimation,
          child: SlideTransition(
            position: _slideAnimation,
            child: child,
          ),
        );
      },
      child: widget.child,
    );
  }
}

// Kullanım:
FadeSlideIn(
  child: Text('Merhaba Dünya!', style: TextStyle(fontSize: 32)),
)

4. Progress Bar Animasyonu

class AnimatedProgressBar extends StatefulWidget {
  final double progress; // 0.0 - 1.0

  AnimatedProgressBar({required this.progress});

  @override
  _AnimatedProgressBarState createState() => _AnimatedProgressBarState();
}

class _AnimatedProgressBarState extends State<AnimatedProgressBar> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _progressAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 500),
      vsync: this,
    );

    _progressAnimation = Tween<double>(
      begin: 0,
      end: widget.progress,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeOut,
    ));

    _controller.forward();
  }

  @override
  void didUpdateWidget(AnimatedProgressBar oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.progress != oldWidget.progress) {
      _progressAnimation = Tween<double>(
        begin: _progressAnimation.value,
        end: widget.progress,
      ).animate(CurvedAnimation(
        parent: _controller,
        curve: Curves.easeOut,
      ));
      _controller.forward(from: 0);
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: 20,
      decoration: BoxDecoration(
        color: Colors.grey[300],
        borderRadius: BorderRadius.circular(10),
      ),
      child: AnimatedBuilder(
        animation: _progressAnimation,
        builder: (context, child) {
          return FractionallySizedBox(
            alignment: Alignment.centerLeft,
            widthFactor: _progressAnimation.value,
            child: Container(
              decoration: BoxDecoration(
                color: Colors.blue,
                borderRadius: BorderRadius.circular(10),
              ),
            ),
          );
        },
      ),
    );
  }
}

5. Shake (Titreme) Animasyonu

class ShakeWidget extends StatefulWidget {
  final Widget child;
  final bool shake;

  ShakeWidget({required this.child, this.shake = false});

  @override
  _ShakeWidgetState createState() => _ShakeWidgetState();
}

class _ShakeWidgetState extends State<ShakeWidget> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _shakeAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 500),
      vsync: this,
    );

    _shakeAnimation = Tween<double>(
      begin: 0,
      end: 10,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.elasticIn,
    ));
  }

  @override
  void didUpdateWidget(ShakeWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.shake && !oldWidget.shake) {
      _controller.forward(from: 0).then((_) => _controller.reverse());
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _shakeAnimation,
      builder: (context, child) {
        return Transform.translate(
          offset: Offset(_shakeAnimation.value * (_controller.value > 0.5 ? -1 : 1), 0),
          child: child,
        );
      },
      child: widget.child,
    );
  }
}

// Kullanım:
class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  bool _showError = false;

  void _login() {
    // Hatalı giriş
    setState(() => _showError = true);
    Future.delayed(Duration(milliseconds: 500), () {
      setState(() => _showError = false);
    });
  }

  @override
  Widget build(BuildContext context) {
    return ShakeWidget(
      shake: _showError,
      child: TextField(
        decoration: InputDecoration(
          labelText: 'Şifre',
          errorText: _showError ? 'Hatalı şifre!' : null,
        ),
      ),
    );
  }
}

Birden Fazla Animasyonu Senkronize Etme

Tek controller ile birden fazla animasyon:

class MultiAnimationDemo extends StatefulWidget {
  @override
  _MultiAnimationDemoState createState() => _MultiAnimationDemoState();
}

class _MultiAnimationDemoState extends State<MultiAnimationDemo> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _rotationAnimation;
  late Animation<double> _scaleAnimation;
  late Animation<Color?> _colorAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 3),
      vsync: this,
    );

    // 0.0 - 0.5 arası dönme
    _rotationAnimation = Tween<double>(
      begin: 0,
      end: 2 * 3.14159,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Interval(0.0, 0.5, curve: Curves.easeInOut),
    ));

    // 0.5 - 1.0 arası büyüme
    _scaleAnimation = Tween<double>(
      begin: 1.0,
      end: 2.0,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Interval(0.5, 1.0, curve: Curves.easeOut),
    ));

    // 0.0 - 1.0 arası renk değişimi
    _colorAnimation = ColorTween(
      begin: Colors.blue,
      end: Colors.red,
    ).animate(_controller);
  }

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

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return Transform.rotate(
              angle: _rotationAnimation.value,
              child: Transform.scale(
                scale: _scaleAnimation.value,
                child: Container(
                  width: 100,
                  height: 100,
                  decoration: BoxDecoration(
                    color: _colorAnimation.value,
                    borderRadius: BorderRadius.circular(20),
                  ),
                ),
              ),
            );
          },
        ),
        SizedBox(height: 40),
        ElevatedButton(
          onPressed: () {
            if (_controller.status == AnimationStatus.completed) {
              _controller.reverse();
            } else {
              _controller.forward();
            }
          },
          child: Text('Animasyonu Başlat'),
        ),
      ],
    );
  }
}

TweenSequence: Adım Adım Animasyon

Farklı aşamalarda farklı değerler:

final _colorAnimation = TweenSequence<Color?>([
  TweenSequenceItem(
    tween: ColorTween(begin: Colors.red, end: Colors.blue),
    weight: 33.3, // %33.3'lük kısım
  ),
  TweenSequenceItem(
    tween: ColorTween(begin: Colors.blue, end: Colors.green),
    weight: 33.3,
  ),
  TweenSequenceItem(
    tween: ColorTween(begin: Colors.green, end: Colors.red),
    weight: 33.4,
  ),
]).animate(_controller);

Performans İpuçları

1. dispose() Unutmayın

@override
void dispose() {
  _controller.dispose(); // Zorunlu!
  super.dispose();
}

Unutursanız hafıza sızıntısı olur.

2. AnimatedBuilder Kullanın

// ❌ Kötü - Tüm widget rebuild edilir
@override
Widget build(BuildContext context) {
  return Transform.rotate(
    angle: _controller.value * 2 * 3.14159,
    child: ExpensiveWidget(),
  );
}

// ✅ İyi - Sadece gerekli kısım rebuild edilir
@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: _controller,
    builder: (context, child) {
      return Transform.rotate(
        angle: _controller.value * 2 * 3.14159,
        child: child,
      );
    },
    child: ExpensiveWidget(), // Cache'lenir
  );
}

3. Gereksiz Animasyon Yapmayın

// ❌ 100 widget aynı anda animasyon yapıyor
ListView.builder(
  itemCount: 100,
  itemBuilder: (context, index) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Transform.scale(
          scale: 1.0 + _controller.value * 0.2,
          child: ListTile(title: Text('Item $index')),
        );
      },
    );
  },
)

// ✅ İyi - Sadece görünür olanlar animasyon yapsın (lazy loading)

4. const Widget'ları Cache'leyin

AnimatedBuilder(
  animation: _controller,
  builder: (context, child) {
    return Transform.rotate(
      angle: _controller.value * 2 * 3.14159,
      child: child,
    );
  },
  child: const Icon(Icons.star, size: 100), // const!
)

Yaygın Hatalar ve Çözümleri

Hata 1: vsync Unutmak

// ❌ Hata: vsync gerekli
_controller = AnimationController(
  duration: Duration(seconds: 2),
);

// ✅ Doğru
class _MyWidgetState extends State<MyWidget> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this, // this, TickerProvider'dır
    );
  }
}

Hata 2: dispose() Unutmak

// ❌ Hafıza sızıntısı!
@override
void dispose() {
  super.dispose();
}

// ✅ Doğru
@override
void dispose() {
  _controller.dispose();
  super.dispose();
}

Hata 3: Yanlış Mixin Seçimi

// ❌ 3 animasyon var ama Single kullanılmış
class _MyWidgetState extends State<MyWidget> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller1;
  late AnimationController _controller2;
  late AnimationController _controller3; // Hata!
}

// ✅ Doğru
class _MyWidgetState extends State<MyWidget> 
    with TickerProviderStateMixin {
  late AnimationController _controller1;
  late AnimationController _controller2;
  late AnimationController _controller3;
}

Hata 4: Duration Çok Uzun

// ❌ 10 saniye çok uzun
AnimationController(
  duration: Duration(seconds: 10),
  vsync: this,
)

// ✅ 2-3 saniye ideal
AnimationController(
  duration: Duration(seconds: 2),
  vsync: this,
)

Özet

  • AnimationController: 0.0 - 1.0 arası değer üreten kontrol mekanizması
  • vsync: Ekranda olmayan animasyonları otomatik durdurur (batarya tasarrufu)
  • Tween: Değer dönüşümü (0-1 → 50-200 gibi)
  • CurvedAnimation: Animasyona karakter katar (easeInOut, elasticOut, vb.)
  • AnimatedBuilder: Performans için sadece gerekli widget'ı rebuild eder
  • Status Listener: Animasyon durumunu dinler (completed, dismissed, vb.)
  • Metodlar: forward(), reverse(), repeat(), reset(), stop()
  • Mixin: SingleTickerProviderStateMixin (1 animasyon) veya TickerProviderStateMixin (birden fazla)
  • dispose(): Hafıza sızıntısını önlemek için zorunlu
  • Performans: const child, AnimatedBuilder kullanın

Explicit animations, Flutter'da animasyon konusunda size tam kontrol verir. Öğrenme eğrisi biraz daha dik olsa da, yaratacağınız etkileyici animasyonlar buna değer!