Ahmet Balaman LogoAhmet Balaman

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

personAhmet Balaman
calendar_today
FlutterAnimationAnimatedContainerImplicit AnimationUI/UX

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:

Explicit Animations (Belirgin Animasyonlar)

  • AnimationController gerektirir
  • Tam kontrol isterseniz kullanılır
  • Daha fazla kod, daha fazla esneklik
  • Örnek: Karmaşık zincir animasyonlar

Implicit Animations (Örtük Animasyonlar)

  • Controller gerekmez
  • 90% kullanım senaryosu 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.

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), () {
      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

// Aşırı gitme ve geri gelme
curve: Curves.anticipate

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.withOpacity(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), () {
      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. Gereksiz AnimatedWidget Kullanmayın

// ❌ Kötü - Sadece renk değişiyorsa Container yeterli
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  width: 100, // Hiç değişmiyor
  height: 100, // Hiç değişmiyor
  color: selectedColor,
)

// ✅ İyi - Sadece gerekli widget'ı animasyonla
Container(
  width: 100,
  height: 100,
  child: AnimatedContainer(
    duration: Duration(milliseconds: 300),
    color: selectedColor,
  ),
)

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 setState + Animated* widget kombinasyonu yeterli!