↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

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

3 dk okuma #flutter
Dizi · 37/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 8 başlık
  1. Sorunu Tanımlayalım
  2. Hero Widget'ı — Tek Bir Kural: Aynı tag
  3. Neden Sadece "Konum ve Boyut"? İçerik de Değişebilir mi?
  4. flightShuttleBuilder — Geçiş Sırasındaki Görünümü Özelleştirme
  5. Hero, Neyle Birlikte Çalışır?
  6. Yaygın Bir Hata — Aynı Ekranda İki Aynı tag
  7. 🎯 Bu Dersten Çıkarılması Gerekenler
  8. 📝 Ödevler

Konu 35-36'da tek bir ekran içindeki animasyonları öğrendik. Bu ders, iki ekran arasındaki en tanıdık geçiş animasyonuna odaklanıyor: bir listedeki küçük bir resmin, detay ekranında büyük haline sorunsuzca dönüşmesi. Bunu muhtemelen Instagram, e-ticaret uygulamaları gibi pek çok yerde görmüşsündür.

Sorunu Tanımlayalım

Bölüm 3 Konu 14'te öğrendiğimiz Navigator.push()'u hatırla — bir ekrandan diğerine geçerken, yeni ekran genelde sağdan kayarak ya da belirir. Ama bir ürün resmi gibi ortak bir öğe varsa, kullanıcı deneyimi açısından o öğenin aniden kaybolup başka bir yerde aniden belirmesi yerine, akıcı bir şekilde büyüyerek/hareket ederek yeni konumuna gitmesi çok daha doğal hissettirir. İşte "Hero animasyonu" tam olarak bunu yapar.

Hero Widget'ı — Tek Bir Kural: Aynı tag

dart
// Liste ekranı
class UrunListesi extends StatelessWidget {
  const UrunListesi({super.key});

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const UrunDetayEkrani()),
            );
          },
          child: Hero(
            tag: 'urun-resmi-1', // ANAHTAR — iki ekranda da AYNI olmalı
            child: Image.asset('assets/urun1.png', width: 80, height: 80),
          ),
        ),
      ],
    );
  }
}

// Detay ekranı
class UrunDetayEkrani extends StatelessWidget {
  const UrunDetayEkrani({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Hero(
          tag: 'urun-resmi-1', // AYNI tag!
          child: Image.asset('assets/urun1.png', width: 300, height: 300),
        ),
      ),
    );
  }
}

Bütün mekanizma, tek bir kurala dayanır: Aynı tag değerine sahip iki Hero widget'ı, farklı ekranlarda (route'larda) bulunuyorsa, Flutter, geçiş sırasında otomatik olarak ilkinin konumundan/boyutundan ikincisinin konumuna/boyutuna animasyonlu bir geçiş oluşturur. Sen width: 80 → width: 300 değişimini elle animasyonlamıyorsun — Flutter, Konu 36'da öğrendiğimiz AnimationController/Tween altyapısını arka planda kendisi kurup çalıştırıyor.

tag neden String yerine Object tipinde de olabilir? Gerçek uygulamalarda genelde 'urun-resmi-1' gibi sabit bir string yerine, ürünün kendi ID'sini kullanırsın (tag: urun.id) — bu sayede her ürün için otomatik olarak benzersiz bir tag üretmiş olursun, elle her birine farklı string yazmana gerek kalmaz.

dart
Hero(
  tag: urun.id, // dinamik, her ürün için otomatik benzersiz
  child: Image.network(urun.resimUrl),
)

Neden Sadece "Konum ve Boyut"? İçerik de Değişebilir mi?

dart
Hero(
  tag: 'profil-resmi',
  child: CircleAvatar(radius: 30, backgroundImage: AssetImage('assets/profil.png')),
)
// ... diğer ekranda
Hero(
  tag: 'profil-resmi',
  child: Container(
    decoration: BoxDecoration(shape: BoxShape.circle, border: Border.all(width: 4)),
    child: CircleAvatar(radius: 100, backgroundImage: AssetImage('assets/profil.png')),
  ),
)

Hero widget'ının içeriği (child) iki ekranda birebir aynı olmak zorunda değil — burada ikinci ekranda bir border eklendi. Flutter, geçiş sırasında iki child'ı da kullanarak (varsayılan olarak hedef ekranın child'ına geçiş yaparak) bir animasyon oluşturur. Ama pratik tavsiye: en akıcı sonuç için, iki taraftaki içeriği mümkün olduğunca benzer tutmak (örn. ikisi de aynı resim, sadece boyutu farklı) en doğal görüneni verir — çok farklı iki widget arasında Hero denemek garip sonuçlar üretebilir.

flightShuttleBuilder — Geçiş Sırasındaki Görünümü Özelleştirme

dart
Hero(
  tag: 'urun-resmi-1',
  flightShuttleBuilder: (
    flightContext,
    animation,
    flightDirection,
    fromHeroContext,
    toHeroContext,
  ) {
    return Material(
      color: Colors.transparent,
      child: toHeroContext.widget, // geçiş sırasında hedef widget'ı göster
    );
  },
  child: Image.asset('assets/urun1.png'),
)

Varsayılan davranış çoğu zaman yeterlidir, ama ileri bir özelleştirme olarak, geçiş sırasında (iki ekran arası "uçuş" anında) tam olarak neyin görüneceğini flightShuttleBuilder ile kontrol edebilirsin. animation parametresi, Konu 36'da öğrendiğimiz Animation<double> tipinde — geçişin o anki ilerleme durumunu (0.0–1.0) verir.

Hero, Neyle Birlikte Çalışır?

Hero animasyonu, sadece Navigator.push()/pop() gibi bir route geçişi sırasında tetiklenir — aynı ekran içinde, setState() ile bir widget'ı büyütmek istiyorsan, bu Hero'nun işi değil, Konu 35-36'da öğrendiğimiz implicit/explicit animasyonların işidir. Bölüm 7'de öğrendiğimiz go_router ile de sorunsuz çalışır — GoRoute'un builder'ı ile açılan her ekran, standart bir MaterialPageRoute gibi davranır, dolayısıyla Hero mekanizması aynen çalışmaya devam eder.

Yaygın Bir Hata — Aynı Ekranda İki Aynı tag

dart
// ❌ HATA — aynı ekranda AYNI tag'e sahip iki Hero
ListView(
  children: [
    Hero(tag: 'urun', child: Image.asset('assets/a.png')),
    Hero(tag: 'urun', child: Image.asset('assets/b.png')), // ÇAKIŞMA!
  ],
)

Bir ListView.builder ile ürün listesi oluşturuyorsan ve her satıra sabit bir tag ('urun') verirsen, Flutter hangi Hero'nun hangisiyle eşleşeceğini bilemez ve çalışma zamanında hata verir. Bu yüzden yukarıda vurguladığımız gibi, her öğe için benzersiz bir tag (genelde ürünün ID'si) kullanmak zorunludur — sabit bir string sadece tek bir Hero'nun olduğu ekranlarda güvenlidir.


🎯 Bu Dersten Çıkarılması Gerekenler

  • Hero, iki farklı ekran (route) arasında, aynı tag'e sahip widget'lar arasında otomatik bir geçiş animasyonu oluşturur.
  • Mekanizma sadece Navigator.push()/pop() (veya go_router gibi bunun üzerine kurulu araçlar) sırasında tetiklenir — aynı ekran içi büyütme/küçültme için implicit/explicit animasyonlar kullanılır.
  • tag, listelerde genelde dinamik olmalıdır (örn. urun.id) — sabit bir tag, aynı ekranda birden fazla Hero varsa çakışma hatası verir.
  • İki taraftaki child birebir aynı olmak zorunda değil, ama en akıcı sonuç için benzer tutulması önerilir.
  • flightShuttleBuilder, geçiş anındaki görünümü özelleştirmek için kullanılan ileri bir seçenektir.

📝 Ödevler

  • [ ] Bir ListView.builder ile en az 5 ürünlük bir liste yap, her ürüne benzersiz bir Hero tag'i (ürün ID'si) ver, tıklandığında detay ekranına Hero animasyonuyla geç.
  • [ ] Bilerek iki farklı öğeye aynı sabit tag'i ver, Flutter'ın verdiği hatayı gözlemle ve nedenini kendi cümlelerinle açıkla.
  • [ ] Detay ekranındaki Hero'nun child'ını, liste ekranındakinden görsel olarak biraz farklı yap (örn. bir border ekle), geçişin nasıl göründüğünü gözlemle.
  • [ ] go_router (Bölüm 7 Konu 30) kullanan bir navigasyon yapısında Hero animasyonunun sorunsuz çalıştığını doğrula.

Sıradaki konu: Bölüm 8 — Konu 38: CustomPainter / Canvas