TB
← Tüm yazılar

Animasyon ve motion

Flutter animasyon altyapisi, implicit ve explicit animasyonlar ile performans odaklı motion tasarımının temel prensiplerini ele alir.

Flutter uygulamalarinda kullanıcının algiladigi kalite cogunlukla statik ekranlarla degil, hareketin ne kadar tutarlı ve amacli olduguyla olculur. Animasyon ve motion tasarımı yalnizca görsel sus degildir; kullanıcıya durum gecislerini, hiyerarsiyi ve geri bildirimi anlatir. Flutter bu alanda güçlü bir altyapi sunar cunku her piksel cizimi zaten kare bazli güncellenir ve AnimationController tabanli explicit animasyonlar ile AnimatedContainer gibi implicit widget'lar ayni render pipeline uzerinden çalışır.

Flutter animasyon mimarisinin temelleri

Flutter'da animasyon iki ana gruba ayrilir: implicit ve explicit. Implicit animasyonlar, belirli bir hedef duruma geciste framework'un aradaki ara degerleri otomatik hesaplamasidir. Örneğin AnimatedOpacity widget'i opacity degerini degistirdiginizde tween hesaplamasini kendisi yapar. Explicit animasyonlarda ise kontrol sizdedir: bir AnimationController oluşturur, TickerProvider ile frame senkronizasyonu sağlar ve Animation<double> veya Animation<Color> nesnelerini dinleyerek UI'yi guncellersiniz.

Bu ayrim performans acisindan da önemlidir. Implicit widget'lar kisa ve okunabilir kod sağlar; ancak ayni ekranda onlarca bagimsiz implicit animasyon varsa rebuild maliyeti artabilir. Explicit animasyonlarda tek bir controller ile birden fazla CurvedAnimation turetebilir, staggered gecisler tasarlayabilirsiniz. Uzun sureli ve karmaşık motion senaryolarinda explicit yaklaşım genellikle daha ongorulebilir CPU profili üretir.

Ticker, vsync ve frame zamanlamasi

AnimationController bir vsync nesnesi ister. StatefulWidget icinde bu rol cogunlukla SingleTickerProviderStateMixin veya çoklu controller gerektiren durumlarda TickerProviderStateMixin ile cozulur. Ticker, cihazin ekran yenileme hizina bağlı olarak animasyonun bir sonraki degerini hesaplar. Bu mekanizma sayesinde animasyonlar 60 fps veya 120 fps ekranlarda gereksiz hesaplama yapmaz; görünür olmayan route'lardaki controller'lar durdurulabilir.

class PulseBadge extends StatefulWidget {
  const PulseBadge({super.key});
  @override
  State<PulseBadge> createState() => _PulseBadgeState();
}

class _PulseBadgeState extends State<PulseBadge>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _scale;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 900),
    )..repeat(reverse: true);
    _scale = Tween(begin: 0.92, end: 1.08).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return ScaleTransition(scale: _scale, child: const Icon(Icons.star));
  }
}

Curves, easing ve algisal hiz

Linear interpolasyon nadiren dogal görünür. Curves.easeOutCubic, Curves.fastOutSlowIn ve Material Design'in onerdigi hareket egrileri, kullanıcının hareketi "fiziksel" olarak algilamasini sağlar. Flutter 3.x ile birlikte Cubic sinifi uzerinden özel egri tanımlamak da mumkundur. Motion tasarımında temel kural sudur: giriş hareketi biraz daha yavas, çıkış hareketi daha hızlı olabilir; ancak modal acilis ve liste elemani silme gibi aksiyonlarda tersine davranmak gerekebilir.

Interval curve'u ile tek bir controller icinde farklı ozelliklerin farklı zaman dilimlerinde hareket etmesini saglayabilirsiniz. Örneğin bir kart once yukari kayarken, 0.3 saniye sonra opakligi artabilir. Bu teknik, staggered animasyonlari tek controller ile yonetmenin en temiz yoludur.

Hero, PageRoute ve geçiş animasyonlari

Sayfa gecislerinde Hero widget'i paylasilan bir tag uzerinden iki route arasinda widget'in konumunu ve boyutunu interpolate eder. Hero kullanırken her iki tarafta da ayni tag'in benzersiz olmasi ve hedef widget'in layout'unun stabil olmasi gerekir. Aksi halde "Hero flight" sirasinda titreme veya ani atlama gorulur. Özel gecisler icin PageRouteBuilder ile transitionsBuilder tanimlayabilir, fade-slide ve shared axis gibi kalıplari uygulayabilirsiniz.

Implicit animasyon kalıplari

Küçük etkilesimlerde implicit widget'lar bakım maliyetini dusurur:

  • AnimatedContainer: boyut, renk, padding ve decoration gecisleri
  • AnimatedAlign ve AnimatedPositioned: layout animasyonlari
  • AnimatedSwitcher: child degisiminde giriş-çıkış efekti
  • AnimatedList: liste elemani ekleme ve silme hareketleri

AnimatedSwitcher kullanırken switchInCurve, switchOutCurve ve transitionBuilder parametreleri ile ayni state degisiminde bile farklı motion dili kurabilirsiniz. Örneğin hata mesajlari fade ile gelirken, başarı mesajlari hafif scale ile gorunebilir.

Explicit animasyon ve AnimationBuilder

Daha fazla kontrol gerektiginde AnimatedBuilder veya ListenableBuilder tercih edilir. Bu widget'lar yalnizca animasyon dinleyicisini yeniden cizer; tüm sayfayi rebuild etmez. Performans acisindan kritik nokta budur: animasyonu dinleyen alt agaci mümkün oldugunca küçük tutmak.

AnimatedBuilder(
  animation: _controller,
  builder: (context, child) {
    return Transform.translate(
      offset: Offset(0, 20 * (1 - _controller.value)),
      child: Opacity(opacity: _controller.value, child: child),
    );
  },
  child: const ExpensiveStaticPanel(),
)

Yukaridaki ornekte child parametresi rebuild dışında tutuldugu icin agir bir alt agac her frame'de yeniden olusturulmaz. Bu pattern, animasyon performansini iyilestirmenin en etkili yontemlerinden biridir.

Physics tabanli animasyonlar

SpringSimulation, FrictionSimulation ve ScrollPhysics tabanli davranislar, kullanıcının parmak hareketini devam ettiren motion tasarımının temelidir. Draggable sheet, pull-to-refresh ve custom scroll snap noktalarinda fizik parametrelerini (stiffness, damping) ince ayarlamak UX'i belirgin şekilde etkiler. Fizik tabanli cozumlerde animasyon bitisini sabit sure yerine hedef konuma yakinsama ile olcmek daha doğru olur.

Rive, Lottie ve vektor tabanli motion

Karmaşık karakter animasyonlari veya tasarimci ciktilari icin rive ve lottie paketleri sik kullanılır. Bu kaynaklar genellikle GPU dostu ve tasarım ekibiyle ortak dil sağlar. Ancak paket boyutu, ilk yükleme suresi ve state machine entegrasyonu değerlendirilmelidir. Rive tarafinda StateMachineController ile tetikleyici ve boolean input'lar uygulama durumuna baglanabilir.

Performans ve jank onleme

Animasyon sirasinda jank olusmasinin baslica nedenleri sunlardir:

  1. Animasyon dinleyicisinde agir is mantigi calistirmak
  2. Büyük blur, shadow ve clip kombinasyonlari
  3. Her frame'de image decode veya network istegi
  4. Gereksiz setState ile tüm sayfayi yeniden cizmek

Performance Overlay ve DevTools Timeline ile UI thread'de 16 ms sınırı asan frame'leri tespit edebilirsiniz. Animasyon kodunu profile modda calistirmak, release moddaki davranistan farklı olabilecegi icin her iki modda da kontrol onerilir.

RepaintBoundary ve layer maliyeti

Sürekli hareket eden bir bilesen, statik arka plandan RepaintBoundary ile ayrildiginda repaint alani kuculur. Özellikle liste icinde animasyonlu ikonlar ve mini grafikler icin bu teknik faydalidir. Buna karsin her yere RepaintBoundary koymak layer sayisini artirarak bellek tuketimini yukari cekebilir; olculu kullanım gerekir.

Erisilebilirlik ve hareket azaltma

MediaQuery.disableAnimationsOf(context) ve platform duzeyindeki "reduce motion" tercihleri dikkate alinmalidir. Kritik bilgi yalnizca animasyonla verilmemeli; renk, metin ve ikon destegi de olmalidir. Uygulama genelinde animasyon suresini kisaltan bir tema katmanı tanımlamak, erisilebilirlik ile marka kimligini dengelemenin pratik yoludur.

Motion tasarım sistemini kodda somutlastirmak

Tutarlı motion icin proje bazinda sabitler tanimlayin:

  • motionFast = Duration(milliseconds: 150)
  • motionStandard = Duration(milliseconds: 250)
  • motionEmphasized = Duration(milliseconds: 400)
  • Ortak Curve setleri: giriş, çıkış, emphasis

Bu sabitleri dogrudan widget icinde magic number olarak birakmak yerine merkezi bir AppMotion sinifinda toplamak, tasarım degisikliklerini tek noktadan yonetmenizi sağlar. Ayrica animasyonlari feature flag ile kapatma imkani sunar.

Test edilebilir animasyonlar

Widget testlerinde tester.pumpAndSettle() animasyonun tamamlanmasini bekler. Deterministik golden testler icin animasyonu belirli bir controller.value noktasinda durdurmak veya timeDilation kullanmak gerekebilir. Flaky testleri onlemek icin animasyonlu ekranlarda settle suresine ust sınır koymak ve timeout ile birlikte düşünmek önemlidir.

Özet yaklaşım

Flutter'da başarılı motion tasarımı; doğru animasyon turunu secmek, rebuild kapsamini daraltmak, fizik ve easing ile algiyi yönetmek ve performans profili cikarmakla mumkundur. Küçük etkilesimlerde implicit widget'lar, cok asamali gecislerde explicit controller'lar ve tasarimci ciktilarinda Rive/Lottie gibi araclar hibrit bir strateji oluşturur. Her animasyonun kullanıcıya hangi bilgiyi tasidigini açıkça tanımlamak, sus ve gurultu arasindaki farki belirler.