↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)

5 dk okuma #flutter
Dizi · 36/61 Flutter Türkçe Tutorial
  1. Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
  2. Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
  3. Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
  4. Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
  5. Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
  6. Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
  7. Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
  8. Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
  9. Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
  10. Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
  11. Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
  12. Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
  13. Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
  14. Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
  15. Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
  16. Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
  17. Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
  18. Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
  19. Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
  20. Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
  21. Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
  22. Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
  23. Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
  24. Bölüm 6 — Konu 24: Provider Paketi
  25. Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
  26. Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
  27. Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
  28. Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
  29. Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
  30. Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
  31. Bölüm 7 — Konu 31: Deep Linking
  32. Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
  33. Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
  34. Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
  35. Bölüm 8 — Konu 35: Implicit Animasyonlar
  36. Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
  37. Bölüm 8 — Konu 37: Hero Animasyonları
  38. Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
  39. Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
  40. Bölüm 9 — Konu 40: Platform Channels
  41. Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
  42. Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
  43. Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
  44. Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
  45. Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
  46. Bölüm 10 — Konu 46: CI/CD
  47. Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
  48. Bölüm 10 — Konu 48: DevTools Profiling
  49. Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
  50. Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
  51. Bölüm 11 — Konu 51: Supabase Alternatifi
  52. Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
  53. Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
  54. Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
  55. Bölüm 12 — Konu 55: Flutter Web / Desktop
  56. Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
  57. Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
  58. Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
  59. Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
  60. Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
  61. Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
Dizinin sayfası →
İçindekiler 11 başlık
  1. Neden Explicit Animasyona İhtiyaç Duyarsın?
  2. AnimationController — Animasyonun "Motoru"
  3. AnimationController Aslında Ne Üretiyor?
  4. Tween — Ham Değeri "Anlamlı" Bir Aralığa Dönüştürme
  5. Animation<T>'in Tipi double Olmak Zorunda Değil
  6. AnimatedBuilder'ın child Parametresi — Performans İnceliği
  7. Durum Dinleme — addStatusListener
  8. Hazır Geçiş Widget'ları — *Transition Ailesi
  9. Implicit'e Geri Dönüş — Artık "Sihir" Değil
  10. 🎯 Bu Dersten Çıkarılması Gerekenler
  11. 📝 Ödevler

Konu 35'te implicit animasyonların "ne istediğini söyle, Flutter halletsin" felsefesini gördük. Bu derste, "nasıl olacağını sen yönet" dünyasına giriyoruz — implicit animasyonların arkasında gerçekte ne olduğunu da bu sayede öğreneceğiz.

Neden Explicit Animasyona İhtiyaç Duyarsın?

AnimatedContainer gibi widget'lar harika ama şu senaryolarda yetersiz kalır:

  • Animasyonu duraklatmak, tersine çevirmek, ya da tekrar tekrar (loop) oynatmak istiyorsun.
  • Birden fazla animasyonu birbirine bağlı (biri bitince diğeri başlasın gibi) yönetmen gerekiyor.
  • Animasyonun hangi aşamada olduğunu (başladı mı, bitti mi) koda göre tepki vermen gerekiyor.

AnimationController — Animasyonun "Motoru"

dart
class DonenIkon extends StatefulWidget {
  const DonenIkon({super.key});

  @override
  State<DonenIkon> createState() => _DonenIkonState();
}

class _DonenIkonState extends State<DonenIkon> with SingleTickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this, // birazdan açıklayacağız
    )..repeat(); // sonsuz döngüde oynat
  }

  @override
  void dispose() {
    _controller.dispose(); // ÇOK ÖNEMLİ — bellek sızıntısını önler
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return RotationTransition(
      turns: _controller,
      child: const Icon(Icons.refresh, size: 48),
    );
  }
}

Parça parça inceleyelim:

with SingleTickerProviderStateMixin — Dart Bölüm 7'de öğrendiğimiz mixin kavramının, Flutter'da kritik bir kullanımı. Bir animasyonun "her karede" (saniyede ~60 kez) güncellenmesi gerekir — bu düzenli "tık"ları üreten mekanizmaya Ticker denir. SingleTickerProviderStateMixin, State sınıfına, bir tane Ticker üretebilme yeteneği ekler (birden fazla AnimationController yönetmen gerekiyorsa TickerProviderStateMixin kullanılır — çoğul).

vsync: this — vsync ("vertical sync"), AnimationController'a hangi Ticker sağlayıcısını kullanacağını söyler. this, yukarıdaki mixin sayesinde artık bir TickerProvider'dır. Neden gerekli? Ekranda görünmeyen bir widget'ın (örneğin başka bir sekmeye geçtin) arka planda gereksiz yere animasyon karesi üretip pil tüketmesini önlemek için — vsync, Flutter'a "bu widget görünür değilken animasyonu duraklat" demenin yoludur.

late final AnimationController _controller — Dart Bölüm 2'de öğrendiğimiz late anahtar kelimesi burada zorunlu, çünkü _controller, initState() içinde (constructor'da değil) oluşturuluyor — late, "bu alana hemen değil, ilk kullanılmadan önce bir değer atanacak" demektir.

..repeat() — Dart Bölüm 6'da öğrendiğimiz cascade operatörü (..) — AnimationController'ı oluşturduktan hemen sonra, aynı ifade içinde repeat() metodunu çağırıyoruz (animasyonu sürekli tekrar ettir).

dispose() içinde _controller.dispose() — Bölüm 2 Konu 6'da State sınıfının yaşam döngüsünü öğrenmiştik; dispose(), widget kalıcı olarak kaldırıldığında çağrılır. AnimationController'ı burada serbest bırakmazsan, widget ekrandan kalksa bile animasyon arka planda çalışmaya devam eder — bu, gerçek uygulamalarda çok yaygın bir bellek sızıntısı kaynağıdır.

AnimationController Aslında Ne Üretiyor?

AnimationController, kendisi bir Animation<double>'dır — 0.0'dan 1.0'a (varsayılan olarak), duration boyunca doğrusal olarak ilerleyen bir değer üretir. RotationTransition'ın turns: parametresi, bu 0.0→1.0 değerini "kaç tam tur" olarak yorumluyor (1.0 = 360°).

dart
_controller.forward();   // 0.0'dan 1.0'a ilerlet
_controller.reverse();   // 1.0'dan 0.0'a geri sar
_controller.repeat();    // sürekli 0.0 → 1.0 → 0.0 → 1.0 ... tekrarla
_controller.stop();      // olduğu yerde durdur

Tween — Ham Değeri "Anlamlı" Bir Aralığa Dönüştürme

AnimationController'ın ürettiği 0.0–1.0 aralığı çoğu zaman doğrudan işine yaramaz — bir rengi, bir boyutu, bir konumu animasyonlamak istiyorsun. Tween (Konu 35'te implicit animasyonlarda da gördüğümüz sınıf), bu ham 0.0–1.0 değerini, istediğin aralığa dönüştürür:

dart
class BuyuyenKutu extends StatefulWidget {
  const BuyuyenKutu({super.key});

  @override
  State<BuyuyenKutu> createState() => _BuyuyenKutuState();
}

class _BuyuyenKutuState extends State<BuyuyenKutu> with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _boyutAnimasyonu;

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

    _boyutAnimasyonu = Tween<double>(begin: 50, end: 200).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );

    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _boyutAnimasyonu,
      builder: (context, child) {
        return Container(
          width: _boyutAnimasyonu.value,
          height: _boyutAnimasyonu.value,
          color: Colors.indigo,
        );
      },
    );
  }
}

Tween<double>(begin: 50, end: 200).animate(...) — Tween'in .animate() metodu, kendisine bir Animation<double> (genelde CurvedAnimation üzerinden _controller'ın kendisi) bağlar ve sonuç olarak, _controller 0.0'dan 1.0'a ilerlerken, 50'den 200'e ilerleyen yeni bir Animation<double> üretir.

CurvedAnimation(parent: _controller, curve: Curves.easeInOut) — _controller'ın doğrusal (linear) ilerlemesine, Konu 35'te gördüğümüz Curves mantığını uygular. Bu, Tween ile AnimationController arasına giren bir "ara katman" olarak düşünebilirsin.

AnimatedBuilder — Bölüm 3 Konu 11'de öğrendiğimiz builder pattern'in burada kritik bir kullanımı. animation: _boyutAnimasyonu verildiğinde, _boyutAnimasyonu her değiştiğinde (saniyede ~60 kez), builder yeniden çağrılır — sen setState() çağırmana hiç gerek kalmadan. _boyutAnimasyonu.value, o anki ara değeri (örn. 127.3) verir.

Neden setState() değil de AnimatedBuilder? Performans. setState() çağırmak, tüm build() metodunu yeniden çalıştırır. AnimatedBuilder, sadece kendi builder'ını (genelde küçük bir widget alt ağacını) yeniden çizer — geri kalan widget ağacın etkilenmez.

Animation<T>'in Tipi double Olmak Zorunda Değil

dart
final Animation<Color?> _renkAnimasyonu = ColorTween(
  begin: Colors.blue,
  end: Colors.red,
).animate(_controller);

final Animation<Offset> _konumAnimasyonu = Tween<Offset>(
  begin: Offset.zero,
  end: const Offset(1.5, 0),
).animate(_controller);

Dart Bölüm 9'da öğrendiğimiz generic tip parametreleri, burada tam güçle çalışıyor — ColorTween, iki renk arasında (Flutter'ın renk enterpolasyon mantığıyla) geçiş üretir; Offset ile ise widget'ın konumunu animasyonlayabilirsin (SlideTransition ile birlikte kullanılır).

AnimatedBuilder'ın child Parametresi — Performans İnceliği

dart
AnimatedBuilder(
  animation: _controller,
  child: const FlutterLogo(size: 100), // SADECE BİR KERE oluşturulur
  builder: (context, child) {
    return Transform.rotate(
      angle: _controller.value * 6.28, // 2*pi
      child: child, // her karede YENİDEN OLUŞTURULMUYOR, sadece döndürülüyor
    );
  },
)

Eğer builder içinde her karede yeniden oluşturulmasına gerek olmayan bir alt widget varsa (burada FlutterLogo, sadece döndürülüyor, kendisi değişmiyor), bunu AnimatedBuilder'ın child: parametresine taşırsın. Flutter, bu child'ı bir kere oluşturur ve her builder çağrısında aynı örneği sana geri verir — Bölüm 9'da (performans) derinlemesine göreceğimiz bir optimizasyon deseninin ilk örneği.

Durum Dinleme — addStatusListener

dart
_controller.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    _controller.reverse(); // bitince otomatik geri sar — "yo-yo" efekti
  } else if (status == AnimationStatus.dismissed) {
    _controller.forward(); // baştan başlat
  }
});

AnimationStatus, Dart Bölüm 7'de öğrendiğimiz bir enum'dur (dismissed, forward, reverse, completed). Bu dinleyici sayesinde, animasyon bittiğinde koda göre tepki verebilirsin — implicit animasyonlarda bu seviyede kontrol mümkün değildi.

Hazır Geçiş Widget'ları — *Transition Ailesi

AnimatedBuilder ile her şeyi elle yazabilirsin, ama Flutter, en sık ihtiyaç duyulan geçişler için hazır widget'lar sunar:

dart
FadeTransition(opacity: _animasyon, child: ...)       // saydamlık
ScaleTransition(scale: _animasyon, child: ...)         // boyut
RotationTransition(turns: _animasyon, child: ...)      // döndürme
SlideTransition(position: _konumAnimasyonu, child: ...) // konum
SizeTransition(sizeFactor: _animasyon, child: ...)     // boyut açılma/kapanma

Bunlar, AnimatedBuilder + ilgili Transform/Opacity widget'ının hazır, isimlendirilmiş kombinasyonlarıdır — kendi AnimatedBuilder'ını yazmadan önce, ihtiyacının bunlardan biriyle karşılanıp karşılanmadığını kontrol etmek iyi bir alışkanlıktır.

Implicit'e Geri Dönüş — Artık "Sihir" Değil

Konu 35'te AnimatedContainer'ın arkasında ne olduğunu merak etmiştin belki — şimdi cevabı biliyorsun: AnimatedContainer, kendi iç AnimationController'ını yönetir, değer değiştiğinde otomatik olarak .forward() çağırır ve bir Tween oluşturup animasyonu senin için oynatır. Konu 35'teki her implicit widget, bu dersteki parçaların Flutter tarafından senin için birleştirilmiş halidir — tıpkı Provider'ın arka planda InheritedWidget kullanması gibi (Bölüm 6 Konu 23'ü hatırla).


🎯 Bu Dersten Çıkarılması Gerekenler

  • AnimationController, 0.0–1.0 arası doğrusal ilerleyen bir Animation<double> üretir; vsync (Ticker sağlayıcı) ile senkronize çalışır ve mutlaka dispose() edilmelidir.
  • Tween<T>.animate(...), ham 0.0–1.0 değerini istediğin aralığa/tipe (double, Color, Offset vb.) dönüştürür; CurvedAnimation, buna bir Curve uygular.
  • AnimatedBuilder, bir Animation değiştiğinde setState() çağırmadan, sadece kendi builder'ını yeniden çizer — performans için child: parametresiyle değişmeyen alt widget'ları izole edebilirsin.
  • addStatusListener, animasyonun aşamasına (completed, dismissed vb.) göre koda tepki vermeni sağlar.
  • FadeTransition/ScaleTransition/RotationTransition/SlideTransition, en sık ihtiyaç duyulan geçişlerin hazır widget'larıdır.
  • Implicit animasyonlar (Konu 35), bu dersteki mekanizmaların Flutter tarafından senin için sarmalanmış halidir.

📝 Ödevler

  • [ ] AnimationController + RotationTransition ile sürekli dönen bir ikon yap, .stop()/.forward() bağlı iki butonla kontrol et.
  • [ ] Tween<double> + CurvedAnimation + AnimatedBuilder ile, bir kutunun boyutunu Curves.bounceOut eğrisiyle büyüten bir animasyon yaz.
  • [ ] addStatusListener kullanarak, bir animasyon bittiğinde otomatik olarak tersine (reverse) başlayan bir "yo-yo" efekti oluştur.
  • [ ] AnimatedBuilder'ın child: parametresini kullanarak, sabit kalan bir widget'ı (örn. bir resim) her karede yeniden oluşturmadan döndür.
  • [ ] Aynı görsel efekti önce elle AnimatedBuilder ile, sonra hazır FadeTransition/ScaleTransition widget'larıyla yap, kod miktarını karşılaştır.

Sıradaki konu: Bölüm 8 — Konu 37: Hero Animasyonları