İçeriğe geç / Skip to content / Zum Inhalt

Flutter: Implicit Animations ile Sıfır Çaba, Maksimum Etki

Ahmet Balaman

Son güncelleme:

5 dk okuma

FlutterAnimationAnimatedContainerImplicit AnimationUI/UX
Flutter: Implicit Animations ile Sıfır Çaba, Maksimum Etki

Animasyon dendiğinde aklınıza karmaşık matematik formülleri, AnimationController'lar ve sayfalar dolusu kod mu geliyor? Ya size tek bir değişiklikle widget'larınızın akıcı bir şekilde animasyon yapabileceğini söylesem?

Flutter'ın Implicit Animations (örtük animasyonlar) özelliği tam da bunu yapıyor. setState çağırdığınızda, widget'ınız eski değerinden yeni değerine otomatik olarak geçiş yapıyor. Sihir mi? Hayır, Flutter mühendisliği.

Explicit vs Implicit: Fark Nerede?

İki tür animasyon vardır:

Implicit animasyonda değeri değiştirmek yeterken explicit animasyonda AnimationController, Tween ve AnimatedBuilder zinciri kurulur

Explicit Animations (Belirgin Animasyonlar)

Implicit Animations (Örtük Animasyonlar)

  • Controller gerekmez
  • Kullanım senaryolarının büyük çoğunluğu için yeterli
  • Az kod, harika sonuç
  • Örnek: Buton rengi, boyut değişimi, konum geçişleri

Altın Kural: Animasyonu baştan sona kontrol etmeniz gerekmiyorsa, implicit animation kullanın.

Canlı Demo

Aşağıdaki interaktif örnekte bu widget'ı deneyebilirsiniz:

💡 Eğer yukarıdaki örnek açılmazsa, DartPad linkine tıklayarak yeni sekmede çalıştırabilirsiniz.

AnimatedContainer: Swiss Army Knife

AnimatedContainer, Container'ın animasyonlu versiyonu. Herhangi bir özelliği değiştirdiğinizde otomatik olarak animasyon yapar. Padding, alignment, decoration gibi özelliklerin normal Container'da ne işe yaradığını hatırlamak isterseniz widget ağacı ve layout yazısı iyi bir tazeleme.

Temel Kullanım

class AnimatedBoxDemo extends StatefulWidget {
  @override
  _AnimatedBoxDemoState createState() => _AnimatedBoxDemoState();
}

class _AnimatedBoxDemoState extends State<AnimatedBoxDemo> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
          setState(() {
            _isExpanded = !_isExpanded;
          });
        },
        child: AnimatedContainer(
          duration: Duration(milliseconds: 300),
          width: _isExpanded ? 200 : 100,
          height: _isExpanded ? 200 : 100,
          decoration: BoxDecoration(
            color: _isExpanded ? Colors.blue : Colors.red,
            borderRadius: BorderRadius.circular(_isExpanded ? 50 : 10),
          ),
          child: Center(
            child: Text(
              'Tap Me',
              style: TextStyle(color: Colors.white),
            ),
          ),
        ),
      ),
    );
  }
}

Tek bir setState çağrısı, üç farklı özelliği aynı anda animasyon yapıyor:

  • Genişlik ve yükseklik
  • Renk
  • Köşe yuvarlama

AnimatedContainer ile Neler Yapılabilir?

AnimatedContainer, Container'ın tüm özelliklerini animasyonla değiştirebilir:

1. Boyut Animasyonu

AnimatedContainer(
  duration: Duration(seconds: 1),
  width: isLarge ? 300 : 100,
  height: isLarge ? 300 : 100,
  child: FlutterLogo(),
)

2. Renk Geçişi

AnimatedContainer(
  duration: Duration(milliseconds: 500),
  color: isActive ? Colors.green : Colors.grey,
  child: Text('Status'),
)

3. Padding Animasyonu

AnimatedContainer(
  duration: Duration(milliseconds: 300),
  padding: EdgeInsets.all(isExpanded ? 32 : 8),
  child: Icon(Icons.star),
)

4. Gradient Animasyonu

AnimatedContainer(
  duration: Duration(milliseconds: 800),
  decoration: BoxDecoration(
    gradient: LinearGradient(
      colors: isDarkMode 
          ? [Colors.black, Colors.grey[900]!] 
          : [Colors.blue, Colors.purple],
    ),
  ),
)

5. Transform (Dönme/Ölçekleme)

AnimatedContainer(
  duration: Duration(milliseconds: 400),
  transform: Matrix4.rotationZ(isRotated ? 3.14 : 0),
  child: Icon(Icons.refresh),
)

Diğer Implicit Animation Widget'ları

Flutter, farklı ihtiyaçlar için özelleşmiş implicit animation widget'ları sunar:

AnimatedOpacity - Görünürlük Geçişleri

Web sitelerinde "fade in" efektleri için mükemmel:

class FadeInDemo extends StatefulWidget {
  @override
  _FadeInDemoState createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  bool _isVisible = false;

  @override
  void initState() {
    super.initState();
    // 1 saniye sonra göster
    Future.delayed(Duration(seconds: 1), () {
      if (mounted) setState(() => _isVisible = true);
    });
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedOpacity(
      opacity: _isVisible ? 1.0 : 0.0,
      duration: Duration(milliseconds: 600),
      child: Text(
        'Merhaba Dünya!',
        style: TextStyle(fontSize: 32),
      ),
    );
  }
}

Kullanım alanları:

  • Bildirim mesajları
  • Loading bitince içerik gösterme
  • Hover efektleri
  • Onboarding ekranları

AnimatedPositioned - Stack İçinde Konum Değişimi

class SlidingMenuDemo extends StatefulWidget {
  @override
  _SlidingMenuDemoState createState() => _SlidingMenuDemoState();
}

class _SlidingMenuDemoState extends State<SlidingMenuDemo> {
  bool _isMenuOpen = false;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        // Ana içerik
        Container(color: Colors.white),
        
        // Kayan menü
        AnimatedPositioned(
          duration: Duration(milliseconds: 300),
          curve: Curves.easeInOut,
          left: _isMenuOpen ? 0 : -250,
          top: 0,
          bottom: 0,
          width: 250,
          child: Container(
            color: Colors.blue,
            child: ListView(
              children: [
                ListTile(title: Text('Menü 1', style: TextStyle(color: Colors.white))),
                ListTile(title: Text('Menü 2', style: TextStyle(color: Colors.white))),
                ListTile(title: Text('Menü 3', style: TextStyle(color: Colors.white))),
              ],
            ),
          ),
        ),
        
        // Hamburger butonu
        Positioned(
          top: 50,
          left: 20,
          child: IconButton(
            icon: Icon(Icons.menu),
            onPressed: () {
              setState(() => _isMenuOpen = !_isMenuOpen);
            },
          ),
        ),
      ],
    );
  }
}

Kullanım alanları:

  • Drawer (çekmece) menüler
  • Floating action button konumu
  • Oyun karakterleri
  • Parallax efektleri

AnimatedAlign - Hizalama Geçişleri

Widget'ı parent içinde farklı konumlara kaydırın:

class BouncingBallDemo extends StatefulWidget {
  @override
  _BouncingBallDemoState createState() => _BouncingBallDemoState();
}

class _BouncingBallDemoState extends State<BouncingBallDemo> {
  Alignment _alignment = Alignment.topLeft;

  void _moveBall() {
    setState(() {
      _alignment = _alignment == Alignment.topLeft 
          ? Alignment.bottomRight 
          : Alignment.topLeft;
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _moveBall,
      child: Container(
        width: 300,
        height: 300,
        color: Colors.grey[200],
        child: AnimatedAlign(
          alignment: _alignment,
          duration: Duration(seconds: 1),
          curve: Curves.bounceOut,
          child: Container(
            width: 50,
            height: 50,
            decoration: BoxDecoration(
              color: Colors.red,
              shape: BoxShape.circle,
            ),
          ),
        ),
      ),
    );
  }
}

AnimatedPadding - Boşluk Animasyonu

AnimatedPadding(
  duration: Duration(milliseconds: 300),
  padding: EdgeInsets.all(isSelected ? 20 : 8),
  child: Card(
    child: Text('Seçili Kart'),
  ),
)

AnimatedDefaultTextStyle - Text Stil Geçişleri

class TextStyleDemo extends StatefulWidget {
  @override
  _TextStyleDemoState createState() => _TextStyleDemoState();
}

class _TextStyleDemoState extends State<TextStyleDemo> {
  bool _isLarge = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _isLarge = !_isLarge),
      child: AnimatedDefaultTextStyle(
        duration: Duration(milliseconds: 300),
        style: TextStyle(
          fontSize: _isLarge ? 48 : 24,
          color: _isLarge ? Colors.red : Colors.blue,
          fontWeight: _isLarge ? FontWeight.bold : FontWeight.normal,
        ),
        child: Text('Bana Dokun'),
      ),
    );
  }
}

AnimatedCrossFade - İki Widget Arası Geçiş

İki widget arasında yumuşak geçiş yapar:

class ToggleViewDemo extends StatefulWidget {
  @override
  _ToggleViewDemoState createState() => _ToggleViewDemoState();
}

class _ToggleViewDemoState extends State<ToggleViewDemo> {
  bool _showFirst = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedCrossFade(
          firstChild: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
            child: Center(child: Text('İlk Widget', style: TextStyle(color: Colors.white))),
          ),
          secondChild: Container(
            width: 200,
            height: 200,
            color: Colors.red,
            child: Center(child: Text('İkinci Widget', style: TextStyle(color: Colors.white))),
          ),
          crossFadeState: _showFirst 
              ? CrossFadeState.showFirst 
              : CrossFadeState.showSecond,
          duration: Duration(milliseconds: 500),
        ),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: () => setState(() => _showFirst = !_showFirst),
          child: Text('Değiştir'),
        ),
      ],
    );
  }
}

Kullanım alanları:

  • Liste ve grid görünümü değiştirme
  • Boş durum (empty state) gösterme/gizleme
  • Login/Register form geçişi
  • Oyun modu değiştirme

AnimatedSwitcher - Herhangi Bir Widget Değişimi

AnimatedCrossFade'den daha esnek:

class CounterWithAnimation extends StatefulWidget {
  @override
  _CounterWithAnimationState createState() => _CounterWithAnimationState();
}

class _CounterWithAnimationState extends State<CounterWithAnimation> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        AnimatedSwitcher(
          duration: Duration(milliseconds: 300),
          transitionBuilder: (child, animation) {
            return ScaleTransition(scale: animation, child: child);
          },
          child: Text(
            '$_count',
            key: ValueKey<int>(_count), // Önemli: Key gerekli!
            style: TextStyle(fontSize: 72, fontWeight: FontWeight.bold),
          ),
        ),
        SizedBox(height: 20),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IconButton(
              icon: Icon(Icons.remove),
              onPressed: () => setState(() => _count--),
            ),
            SizedBox(width: 20),
            IconButton(
              icon: Icon(Icons.add),
              onPressed: () => setState(() => _count++),
            ),
          ],
        ),
      ],
    );
  }
}

Pro İpucu: AnimatedSwitcher için key parametresi zorunludur. Flutter, key sayesinde widget'ın değiştiğini anlar.

Duration ve Curve: Animasyonun Ruhu

Duration (Süre)

Animasyonun ne kadar süreceğini belirler:

// Çok hızlı - Dikkat çekmeden değişim
Duration(milliseconds: 150)

// Standart - Çoğu kullanım için ideal
Duration(milliseconds: 300)

// Yavaş - Dikkat çekmek için
Duration(milliseconds: 600)

// Çok yavaş - Dramatik efektler
Duration(seconds: 1)

Kullanıcı Deneyimi İpucu:

  • Küçük değişimler: 150-200ms
  • Orta değişimler: 300-400ms
  • Büyük değişimler: 500-800ms
  • 1 saniyeden uzun: Kullanıcıyı bekletir

Curve (Eğri)

Animasyonun hızlanma/yavaşlama davranışı:

// Doğrusal - Sabit hız (genelde kullanılmaz)
curve: Curves.linear

// Yavaş başla, hızlan, yavaş bitir - En doğal
curve: Curves.easeInOut

// Yavaş başla
curve: Curves.easeIn

// Yavaş bitir
curve: Curves.easeOut

// Elastik - Geri sekme hissi
curve: Curves.elasticOut

// Bounce - Zıplama efekti
curve: Curves.bounceOut

// Hızlı başla, yavaş bitir
curve: Curves.decelerate

// Hedefi biraz aşıp geri gelme
curve: Curves.easeOutBack

Curve Karşılaştırma

class CurveComparison extends StatefulWidget {
  @override
  _CurveComparisonState createState() => _CurveComparisonState();
}

class _CurveComparisonState extends State<CurveComparison> {
  bool _expanded = false;

  Widget _buildAnimatedBox(String label, Curve curve) {
    return Column(
      children: [
        Text(label, style: TextStyle(fontSize: 12)),
        AnimatedContainer(
          duration: Duration(seconds: 1),
          curve: curve,
          width: _expanded ? 200 : 50,
          height: 50,
          color: Colors.blue,
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        _buildAnimatedBox('linear', Curves.linear),
        SizedBox(height: 10),
        _buildAnimatedBox('easeInOut', Curves.easeInOut),
        SizedBox(height: 10),
        _buildAnimatedBox('bounceOut', Curves.bounceOut),
        SizedBox(height: 10),
        _buildAnimatedBox('elasticOut', Curves.elasticOut),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: () => setState(() => _expanded = !_expanded),
          child: Text('Animasyonları Başlat'),
        ),
      ],
    );
  }
}

Gerçek Dünya Kullanım Senaryoları

1. Genişleyen Kart (Expandable Card)

class ExpandableCard extends StatefulWidget {
  final String title;
  final String content;

  ExpandableCard({required this.title, required this.content});

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

class _ExpandableCardState extends State<ExpandableCard> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _isExpanded = !_isExpanded),
      child: AnimatedContainer(
        duration: Duration(milliseconds: 300),
        curve: Curves.easeInOut,
        padding: EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(12),
          boxShadow: [
            BoxShadow(
              color: Colors.black12,
              blurRadius: _isExpanded ? 10 : 5,
              spreadRadius: _isExpanded ? 2 : 0,
            ),
          ],
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  widget.title,
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
                AnimatedRotation(
                  duration: Duration(milliseconds: 300),
                  turns: _isExpanded ? 0.5 : 0,
                  child: Icon(Icons.expand_more),
                ),
              ],
            ),
            AnimatedCrossFade(
              firstChild: SizedBox.shrink(),
              secondChild: Padding(
                padding: EdgeInsets.only(top: 12),
                child: Text(widget.content),
              ),
              crossFadeState: _isExpanded 
                  ? CrossFadeState.showSecond 
                  : CrossFadeState.showFirst,
              duration: Duration(milliseconds: 300),
            ),
          ],
        ),
      ),
    );
  }
}

2. Like Butonu Animasyonu

class LikeButton extends StatefulWidget {
  @override
  _LikeButtonState createState() => _LikeButtonState();
}

class _LikeButtonState extends State<LikeButton> {
  bool _isLiked = false;
  int _likeCount = 42;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _isLiked = !_isLiked;
          _likeCount += _isLiked ? 1 : -1;
        });
      },
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          AnimatedContainer(
            duration: Duration(milliseconds: 200),
            curve: Curves.easeInOut,
            padding: EdgeInsets.all(8),
            decoration: BoxDecoration(
              color: _isLiked ? Colors.red.withValues(alpha: 0.2) : Colors.transparent,
              shape: BoxShape.circle,
            ),
            child: AnimatedSwitcher(
              duration: Duration(milliseconds: 200),
              transitionBuilder: (child, animation) {
                return ScaleTransition(scale: animation, child: child);
              },
              child: Icon(
                _isLiked ? Icons.favorite : Icons.favorite_border,
                key: ValueKey<bool>(_isLiked),
                color: _isLiked ? Colors.red : Colors.grey,
                size: 28,
              ),
            ),
          ),
          SizedBox(width: 4),
          AnimatedDefaultTextStyle(
            duration: Duration(milliseconds: 200),
            style: TextStyle(
              color: _isLiked ? Colors.red : Colors.grey,
              fontWeight: _isLiked ? FontWeight.bold : FontWeight.normal,
              fontSize: 16,
            ),
            child: Text('$_likeCount'),
          ),
        ],
      ),
    );
  }
}

3. Loading Placeholder (Skeleton Screen)

class ShimmerLoading extends StatefulWidget {
  @override
  _ShimmerLoadingState createState() => _ShimmerLoadingState();
}

class _ShimmerLoadingState extends State<ShimmerLoading> {
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    // 3 saniye sonra içeriği göster
    Future.delayed(Duration(seconds: 3), () {
      if (mounted) setState(() => _isLoading = false);
    });
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedCrossFade(
      firstChild: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            width: double.infinity,
            height: 200,
            decoration: BoxDecoration(
              color: Colors.grey[300],
              borderRadius: BorderRadius.circular(12),
            ),
          ),
          SizedBox(height: 12),
          Container(
            width: 200,
            height: 20,
            color: Colors.grey[300],
          ),
          SizedBox(height: 8),
          Container(
            width: 150,
            height: 20,
            color: Colors.grey[300],
          ),
        ],
      ),
      secondChild: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Image.network(
            'https://picsum.photos/400/200',
            height: 200,
            width: double.infinity,
            fit: BoxFit.cover,
          ),
          SizedBox(height: 12),
          Text('Başlık Buraya Gelecek', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          SizedBox(height: 8),
          Text('Alt başlık buraya gelecek', style: TextStyle(color: Colors.grey)),
        ],
      ),
      crossFadeState: _isLoading 
          ? CrossFadeState.showFirst 
          : CrossFadeState.showSecond,
      duration: Duration(milliseconds: 500),
    );
  }
}

4. Floating Action Button Morph

class MorphingFAB extends StatefulWidget {
  @override
  _MorphingFABState createState() => _MorphingFABState();
}

class _MorphingFABState extends State<MorphingFAB> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: Duration(milliseconds: 300),
      curve: Curves.easeInOut,
      width: _isExpanded ? 200 : 56,
      height: 56,
      decoration: BoxDecoration(
        color: Colors.blue,
        borderRadius: BorderRadius.circular(28),
      ),
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          borderRadius: BorderRadius.circular(28),
          onTap: () => setState(() => _isExpanded = !_isExpanded),
          child: Padding(
            padding: EdgeInsets.symmetric(horizontal: 16),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(Icons.add, color: Colors.white),
                if (_isExpanded) ...[
                  SizedBox(width: 12),
                  Text(
                    'Yeni Ekle',
                    style: TextStyle(color: Colors.white, fontSize: 16),
                  ),
                ],
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Performans İpuçları

1. const Constructor Kullanın

// ❌ Kötü - Her rebuild'de yeni instance
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  child: Text('Sabit Metin'),
)

// ✅ İyi - Const child cache'lenir
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  child: const Text('Sabit Metin'),
)

2. Sabit Özellikler Sorun Değil, İşe Uygun Widget'ı Seçin

AnimatedContainer yalnızca değeri değişen özellikleri ara değerlerle canlandırır. Hiç değişmeyen width ve height kayda değer bir maliyet getirmez; onları dıştaki ayrı bir Container'a taşımanın performansa faydası yoktur. Asıl kazanç, yalnızca tek bir şeyi canlandırıyorsanız o işe özel widget'ı seçmektir: saydamlık için AnimatedOpacity, hizalama için AnimatedAlign, iç boşluk için AnimatedPadding. Kod niyetini daha açık anlatır.

// Yalnızca renk değişiyor: AnimatedContainer uygun, sabit ölçüler sorun değil
AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  width: 100,
  height: 100,
  color: selectedColor,
)

// Yalnızca saydamlık değişiyor: işe özel widget daha açık
AnimatedOpacity(
  opacity: isVisible ? 1.0 : 0.0,
  duration: const Duration(milliseconds: 300),
  child: const FlutterLogo(size: 100),
)

3. RepaintBoundary ile Optimize Edin

RepaintBoundary(
  child: AnimatedOpacity(
    opacity: isVisible ? 1.0 : 0.0,
    duration: Duration(milliseconds: 300),
    child: HeavyWidget(),
  ),
)

4. Çok Fazla Eş Zamanlı Animasyon Yapmayın

// ❌ Kötü - 100 widget aynı anda animasyon yapıyor
ListView.builder(
  itemCount: 100,
  itemBuilder: (context, index) {
    return AnimatedContainer(
      duration: Duration(seconds: 1),
      height: heights[index],
      child: ListTile(title: Text('Item $index')),
    );
  },
)

// ✅ İyi - Sadece görünür olanlar animasyon yapsın

Yaygın Hatalar ve Çözümleri

Hata 1: AnimatedSwitcher'da Key Unutmak

// ❌ Yanlış - Animasyon çalışmaz
AnimatedSwitcher(
  duration: Duration(milliseconds: 300),
  child: Text('$count'), // Key yok!
)

// ✅ Doğru
AnimatedSwitcher(
  duration: Duration(milliseconds: 300),
  child: Text('$count', key: ValueKey(count)),
)

Hata 2: Duration Çok Uzun

// ❌ Kullanıcı 3 saniye bekliyor
AnimatedContainer(
  duration: Duration(seconds: 3),
  color: newColor,
)

// ✅ Hızlı ve responsive
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  color: newColor,
)

Hata 3: Curve Seçimi Yanlış

// ❌ Linear çok mekanik görünür
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  curve: Curves.linear,
  width: newWidth,
)

// ✅ Doğal görünür
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  curve: Curves.easeInOut,
  width: newWidth,
)

Tüm Implicit Animation Widget'ları

Flutter'da 15+ implicit animation widget'ı var:

Widget Neyi Animasyonlar
AnimatedContainer Tüm Container özellikleri
AnimatedOpacity Opacity (saydamsızlık)
AnimatedPadding Padding (boşluk)
AnimatedPositioned Stack içinde konum
AnimatedAlign Hizalama
AnimatedDefaultTextStyle Text stili
AnimatedPhysicalModel Gölge ve elevation
AnimatedCrossFade İki widget arası geçiş
AnimatedSwitcher Herhangi bir widget değişimi
AnimatedSize Widget boyutu
AnimatedRotation Dönme açısı
AnimatedScale Ölçek
AnimatedSlide Kayma (offset)
AnimatedFractionallySizedBox Parent'a oranla boyut
AnimatedTheme Tema değişiklikleri

Özet

  • Implicit animations: Kod yazmadan animasyon (setState yeterli)
  • AnimatedContainer: En çok yönlü implicit animation widget'ı
  • Duration: Animasyon süresi (ideal: 200-400ms)
  • Curve: Hızlanma/yavaşlama davranışı (ideal: easeInOut)
  • AnimatedOpacity: Fade in/out efektleri için
  • AnimatedPositioned: Stack içinde konum değişimi
  • AnimatedCrossFade: İki widget arası geçiş
  • AnimatedSwitcher: Herhangi bir widget değişimi (key gerekli!)
  • Performans: const, RepaintBoundary, gereksiz animasyon yok
  • UX: Kısa süreler, doğal curve'ler, amaçlı kullanım

Implicit animations, Flutter'ın en güçlü özelliklerinden biri. Minimum kodla maksimum etki yaratır; uygulamanıza hayat katmak için çoğu zaman setState + Animated* kombinasyonu yeterli. Animasyon tek bir ekranın içinde değil de iki sayfa arasında olacaksa, aynı görseli sayfadan sayfaya taşıyan Hero animasyonu bu işin özel aracı.

Sık Sorulan Sorular

Implicit animation ne zaman yetmez?

Animasyonu durdurmanız, geri sarmanız, tekrar etmeniz veya birden fazla animasyonu birbirine zincirlemeniz gerekiyorsa implicit widget'lar yetersiz kalır. Bu durumda AnimationController ile explicit animasyona geçin.

AnimatedContainer neden hiç animasyon yapmıyor?

En sık sebep, widget'ın yeniden build edilmemesidir: değeri setState dışında değiştirdiyseniz Flutter eski ve yeni değeri karşılaştıramaz. İkinci sebep, her build'de yeni bir key verilmesidir; bu durumda Flutter widget'ı aynı widget olarak tanımaz ve geçiş yerine sıfırdan çizer.

Duration için hangi süre doğru?

Küçük geri bildirimler (renk, ölçek) için 150–250 ms, panel açılması gibi daha büyük hareketler için 250–400 ms iyi bir başlangıçtır. Bir saniyeyi aşan süreler uygulamayı hızlı değil yavaş hissettirir.

Yorumlar