Flutter'da animasyonlar

1. Giriş

Animasyonlar, uygulamanızın kullanıcı deneyimini iyileştirmenin, kullanıcıya önemli bilgileri aktarmanın ve uygulamanızı daha şık ve kullanımı keyifli hale getirmenin mükemmel bir yoludur.

Flutter'ın animasyon çerçevesine genel bakış

Flutter, her karede widget ağacının bir bölümünü yeniden oluşturarak animasyon efektlerini gösterir. Animasyon oluşturmayı ve derlemeyi kolaylaştırmak için önceden oluşturulmuş animasyon efektleri ve diğer API'ler sunar.

  • Dolaysız animasyonlar, animasyonların tamamını otomatik olarak çalıştıran önceden oluşturulmuş animasyon efektleridir. Animasyonun hedef değeri değiştiğinde, animasyon mevcut değerden hedef değere kadar çalışır ve widget'ın sorunsuz bir şekilde animasyon yapması için aradaki her değeri gösterir. Örtülü animasyonlara örnek olarak AnimatedSize, AnimatedScale ve AnimatedPositioned verilebilir.
  • Açık animasyonları da önceden oluşturulmuş animasyon efektleridir ancak çalışması için bir Animation nesnesi gerekir. Örnekler: SizeTransition, ScaleTransition veya PositionedTransition.
  • Animation, çalışan veya durdurulan bir animasyonu temsil eden bir sınıftır. Animasyonun çalıştığı hedef değeri temsil eden bir value ve animasyonun herhangi bir zamanda ekranda gösterdiği mevcut değeri temsil eden status olmak üzere iki bileşenden oluşur. Listenable sınıfının alt sınıfıdır ve animasyon çalışırken durum değiştiğinde dinleyicilerini bilgilendirir.
  • AnimationController, animasyon oluşturmanın ve durumunu kontrol etmenin bir yoludur. forward(), reset(), stop() ve repeat() gibi yöntemleri, gösterilen animasyon efektini (ör. ölçek, boyut veya konum) tanımlamak zorunda kalmadan animasyonu kontrol etmek için kullanılabilir.
  • Tweenler, başlangıç ve bitiş değeri arasındaki değerleri ara değerle doldurmak için kullanılır ve çift, Offset veya Color gibi herhangi bir türü temsil edebilir.
  • Eğriler, bir parametrenin zaman içindeki değişim hızını ayarlamak için kullanılır. Bir animasyon çalışırken, animasyonun başında veya sonunda değişim hızını daha hızlı ya da daha yavaş hale getirmek için yavaşlatma eğrisi uygulamak yaygın bir uygulamadır. Eğriler, 0,0 ile 1,0 arasında bir giriş değeri alır ve 0,0 ile 1,0 arasında bir çıkış değeri döndürür.

Oluşturacağınız uygulama

Bu codelab'de, çeşitli animasyon efektleri ve teknikleri içeren bir çoktan seçmeli test oyunu oluşturacaksınız.

3026390ad413769c.gif

Aşağıdakileri nasıl yapacağınızı öğreneceksiniz:

  • Boyutunu ve rengini animasyonlu olarak değiştiren bir widget oluşturma
  • 3D kart çevirme efekti oluşturma
  • Animasyon paketindeki önceden oluşturulmuş şık animasyon efektlerini kullanın
  • Android'in en son sürümünde bulunan tahmini geri gitme hareketi desteğini ekleme

Neler öğreneceksiniz?

Bu codelab'de şunları öğreneceksiniz:

  • Çok fazla kod yazmadan harika animasyonlar oluşturmak için dolaylı olarak animasyonlu efektleri kullanma.
  • AnimatedSwitcher veya AnimationController gibi önceden oluşturulmuş animasyonlu widget'ları kullanarak kendi efektlerinizi yapılandırmak için açıkça animasyonlu efektleri kullanma.
  • 3D efekt gösteren kendi widget'ınızı tanımlamak için AnimationController öğesini kullanma.
  • Az sayıda ayarlamayla şık animasyon efektleri göstermek için animations paketini kullanma.

Gerekenler

  • Flutter SDK'sı
  • VSCode veya Android Studio / IntelliJ gibi bir IDE

2. Flutter geliştirme ortamınızı kurma

Bu laboratuvarı tamamlamak için Flutter SDK ve bir düzenleyici yazılımına ihtiyacınız vardır.

Aşağıdaki cihazlardan herhangi birini kullanarak kod laboratuvarını çalıştırabilirsiniz:

  • Bilgisayarınıza bağlı ve Geliştirici moduna ayarlanmış fiziksel bir Android (7. adımda tahmini geri getirmeyi uygulamak için önerilir) veya iOS cihaz.
  • iOS simülasyon aracı (Xcode araçlarının yüklenmesi gerekir).
  • Android Emülatörü (Android Studio'da kurulum gerektirir).
  • Tarayıcı (Hata ayıklama için Chrome gerekir).
  • Windows, Linux veya macOS masaüstü bilgisayar. Uygulamayı dağıtmayı planladığınız platformda geliştirme yapmanız gerekir. Bu nedenle, Windows masaüstü uygulaması geliştirmek istiyorsanız uygun derleme zincirine erişmek için Windows'ta geliştirme yapmanız gerekir. docs.flutter.dev/desktop adresinde işletim sistemine özgü gereksinimler ayrıntılı olarak açıklanmıştır.

Yüklemenizi doğrulama

Flutter SDK'nızın doğru şekilde yapılandırıldığından ve yukarıdaki hedef platformlardan en az birinin yüklü olduğundan emin olmak için Flutter Doctor aracını kullanın:

$ flutter doctor

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.24.2, on macOS 14.6.1 23G93 darwin-arm64, locale
    en)
[✓] Android toolchain - develop for Android devices
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio
[✓] IntelliJ IDEA Ultimate Edition
[✓] VS Code
[✓] Connected device (4 available)
[✓] Network resources

• No issues found!

3. Başlangıç uygulamasını çalıştırma

Başlatıcı uygulamasını indirin

Başlangıç uygulamasını GitHub'daki flutter/samples deposundan klonlamak için git simgesini kullanın.

$ git clone https://github.com/flutter/codelabs.git
$ cd codelabs/animations/step_01/

Alternatif olarak kaynak kodunu .zip dosyası olarak indirebilirsiniz.

Uygulamayı çalıştırma

Uygulamayı çalıştırmak için flutter run komutunu kullanın ve android, ios veya chrome gibi bir hedef cihaz belirtin. Desteklenen platformların tam listesi için Desteklenen platformlar sayfasına bakın.

$ flutter run -d android

Ayrıca, tercih ettiğiniz IDE'yi kullanarak uygulamayı çalıştırabilir ve hata ayıklayabilirsiniz. Daha fazla bilgi için resmi Flutter belgelerini inceleyin.

Kodu gezme

Başlatıcı uygulama, model-görüntü-görüntü-model veya MVVM tasarım kalıbını izleyen iki ekrandan oluşan bir çoklu seçimli test oyunudur. QuestionScreen (Görünüm), kullanıcıya QuestionBank (Model) sınıfındaki çoktan seçmeli sorular sormak için QuizViewModel (Görünüm-Model) sınıfını kullanır.

  • home_screen.dart: Yeni Oyun düğmesinin yer aldığı bir ekran gösterir.
  • main.dart: MaterialApp'yi Material 3'ü kullanacak ve ana ekranı gösterecek şekilde yapılandırır
  • model.dart: Uygulama genelinde kullanılan temel sınıfları tanımlar.
  • question_screen.dart: Bilgi yarışması oyununun kullanıcı arayüzünü gösterir.
  • view_model.dart: QuestionScreen tarafından görüntülenen test oyununun durumunu ve mantığını depolar.

fbb1e1f7b6c91e21.png

Uygulama henüz animasyonlu efektleri desteklemiyor. Bunun tek istisnası, kullanıcı Yeni Oyun düğmesine bastığında Flutter'ın Navigator sınıfı tarafından görüntülenen varsayılan görünüm geçişidir.

4. Örtük animasyon efektleri kullanma

Özel yapılandırma gerektirmediklerinden, birçok durumda dolaylı animasyonlar mükemmel bir seçimdir. Bu bölümde, StatusBar widget'ını animasyonlu bir skor tablosu gösterecek şekilde güncelleyeceksiniz. Sık kullanılan gizli animasyon efektlerini bulmak için ImplicitlyAnimatedWidget API dokümanlarına göz atın.

206dd8d9c1fae95.gif

Animasyonlu olmayan skor tablosu widget'ını oluşturma

Aşağıdaki kodu içeren yeni bir dosya (lib/scoreboard.dart) oluşturun:

lib/scoreboard.dart

import 'package:flutter/material.dart';

class Scoreboard extends StatelessWidget {
  final int score;
  final int totalQuestions;

  const Scoreboard({
    super.key,
    required this.score,
    required this.totalQuestions,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          for (var i = 0; i < totalQuestions; i++)
            Icon(
              Icons.star,
              size: 50,
              color:
                  score < i + 1 ? Colors.grey.shade400 : Colors.yellow.shade700,
            )
        ],
      ),
    );
  }
}

Ardından, Scoreboard widget'ını StatusBar widget'ının alt öğelerine ekleyin. Böylece, daha önce puanı ve toplam soru sayısını gösteren Text widget'larını değiştirmiş olursunuz. Düzenleyiciniz, dosyanın üst kısmına gerekli import "scoreboard.dart" öğesini otomatik olarak ekler.

lib/question_screen.dart

class StatusBar extends StatelessWidget {
  final QuizViewModel viewModel;

  const StatusBar({required this.viewModel, super.key});

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      child: Padding(
        padding: EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Scoreboard(                                        // NEW
              score: viewModel.score,                          // NEW
              totalQuestions: viewModel.totalQuestions,        // NEW
            ),
          ],
        ),
      ),
    );
  }
}

Bu widget'ta her soru için bir yıldız simgesi gösterilir. Bir soru doğru yanıtlandığında, animasyon olmadan anında başka bir yıldız yanar. Aşağıdaki adımlarda, boyutunu ve rengini animasyonlu olarak değiştirerek kullanıcıya puanının değiştiğini bildireceksiniz.

Dolaysız animasyon efekti kullanma

Yıldız etkinleştiğinde scale tutarını 0.5 yerine 1.0 olarak değiştirmek için AnimatedScale widget'ı kullanan AnimatedStar adlı yeni bir widget oluşturun:

lib/scoreboard.dart

​​import 'package:flutter/material.dart';

class Scoreboard extends StatelessWidget {
  final int score;
  final int totalQuestions;

  const Scoreboard({
    super.key,
    required this.score,
    required this.totalQuestions,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          for (var i = 0; i < totalQuestions; i++)
            AnimatedStar(                                      // NEW
              isActive: score > i,                             // NEW
            )                                                  // NEW
        ],
      ),
    );
  }
}

class AnimatedStar extends StatelessWidget {                   // Add from here...
  final bool isActive;
  final Duration _duration = const Duration(milliseconds: 1000);
  final Color _deactivatedColor = Colors.grey.shade400;
  final Color _activatedColor = Colors.yellow.shade700;

  AnimatedStar({super.key, required this.isActive});

  @override
  Widget build(BuildContext context) {
    return AnimatedScale(
      scale: isActive ? 1.0 : 0.5,
      duration: _duration,
      child: Icon(
        Icons.star,
        size: 50,
        color: isActive ? _activatedColor : _deactivatedColor,
      ),
    );
  }
}                                                              // To here.

Artık kullanıcı bir soruyu doğru yanıtladığında AnimatedStar widget'ı, boyutunu gizli bir animasyon kullanarak güncelliyor. Icon'nin color'si burada animasyonlu değildir. Yalnızca AnimatedScale widget'ı tarafından yapılan scale animasyonlu olur.

84aec4776e70b870.gif

İki değer arasında interpolasyon yapmak için tween kullanın

isActive alanının true olarak değişmesinden hemen sonra AnimatedStar widget'ının renginin değiştiğini fark edin.

Animasyonlu bir renk efekti elde etmek için AnimatedContainer widget'ını (ImplicitlyAnimatedWidget sınıfının başka bir alt sınıfıdır) kullanmayı deneyebilirsiniz. Bu widget, renk de dahil olmak üzere tüm özelliklerini otomatik olarak animasyonlu hale getirebilir. Maalesef widget'ımızın kapsayıcı değil, simge göstermesi gerekiyor.

Simgelerin şekilleri arasında geçiş efektleri uygulayan AnimatedIcon seçeneğini de deneyebilirsiniz. Ancak AnimatedIcons sınıfında yıldız simgesi için varsayılan bir uygulama yoktur.

Bunun yerine, parametre olarak Tween alan TweenAnimationBuilder adlı ImplicitlyAnimatedWidget alt sınıfını kullanacağız. Ara değer, iki değer (begin ve end) alan ve animasyon tarafından görüntülenebilmesi için aralarındaki değerleri hesaplayan bir sınıftır. Bu örnekte, animasyon efektimizi oluşturmak için gereken Tween<Color> arayüzünü karşılayan bir ColorTween kullanacağız.

Icon widget'ını seçin ve IDE'nizde "Geliştirici ile sarma" hızlı işlemini kullanarak adı TweenAnimationBuilder olarak değiştirin. Ardından süreyi ve ColorTween değerini girin.

lib/scoreboard.dart

class AnimatedStar extends StatelessWidget {
  final bool isActive;
  final Duration _duration = const Duration(milliseconds: 1000);
  final Color _deactivatedColor = Colors.grey.shade400;
  final Color _activatedColor = Colors.yellow.shade700;

  AnimatedStar({super.key, required this.isActive});

  @override
  Widget build(BuildContext context) {
    return AnimatedScale(
      scale: isActive ? 1.0 : 0.5,
      duration: _duration,
      child: TweenAnimationBuilder(                            // Add from here...
        duration: _duration,
        tween: ColorTween(
          begin: _deactivatedColor,
          end: isActive ? _activatedColor : _deactivatedColor,
        ),
        builder: (context, value, child) {                     // To here.
          return Icon(
            Icons.star,
            size: 50,
            color: value,                                      // Modify from here...
          );
        },                                                     // To here.
      ),
    );
  }
}

Ardından, yeni animasyonu görmek için uygulamayı sıcak yeniden yükleyin.

8b0911f4af299a60.gif

ColorTween öğemizin end değerinin, isActive parametresinin değerine göre değiştiğini unutmayın. Bunun nedeni, TweenAnimationBuilder öğesinin Tween.end değeri değiştiğinde animasyonunu yeniden çalıştırmasıdır. Bu durumda yeni animasyon, mevcut animasyon değerinden yeni bitiş değerine kadar çalışır. Bu sayede rengi istediğiniz zaman (animasyon çalışırken bile) değiştirebilir ve doğru ara değerlerle sorunsuz bir animasyon efekti görüntüleyebilirsiniz.

Eğri uygulama

Bu animasyon efektlerinin ikisi de sabit bir hızda çalışır ancak animasyonlar genellikle hızlandığında veya yavaşlatıldığında görsel açıdan daha ilgi çekici ve bilgilendirici olur.

Curve, bir parametrenin zaman içindeki değişim hızını tanımlayan bir geçiş işlevi uygular. Flutter, Curves sınıfında easeIn veya easeOut gibi önceden oluşturulmuş bir yumuşatma eğrileri koleksiyonuyla birlikte sunulur.

5dabe68d1210b8a1.gif

3a9e7490c594279a.gif

Bu diyagramlar (Curves API doküman sayfasında mevcuttur), eğrilerin işleyiş şekli hakkında ipucu verir. Eğriler, 0,0 ile 1,0 arasında bir giriş değerini (x ekseninde gösterilir) 0,0 ile 1,0 arasında bir çıkış değerine (y ekseninde gösterilir) dönüştürür. Bu şemalarda, yumuşatma eğrisi kullanıldığında çeşitli animasyon efektlerinin nasıl göründüğüne dair bir önizleme de gösterilir.

AnimatedStar widget'ında _curve adlı yeni bir alan oluşturun ve bu alanı AnimatedScale ve TweenAnimationBuilder widget'larına parametre olarak iletin.

lib/scoreboard.dart

class AnimatedStar extends StatelessWidget {
  final bool isActive;
  final Duration _duration = const Duration(milliseconds: 1000);
  final Color _deactivatedColor = Colors.grey.shade400;
  final Color _activatedColor = Colors.yellow.shade700;
  final Curve _curve = Curves.elasticOut;                       // NEW

  AnimatedStar({super.key, required this.isActive});

  @override
  Widget build(BuildContext context) {
    return AnimatedScale(
      scale: isActive ? 1.0 : 0.5,
      curve: _curve,                                           // NEW
      duration: _duration,
      child: TweenAnimationBuilder(
        curve: _curve,                                         // NEW
        duration: _duration,
        tween: ColorTween(
          begin: _deactivatedColor,
          end: isActive ? _activatedColor : _deactivatedColor,
        ),
        builder: (context, value, child) {
          return Icon(
            Icons.star,
            size: 50,
            color: value,
          );
        },
      ),
    );
  }
}

Bu örnekte elasticOut eğrisi, bir yay hareketiyle başlayan ve sona doğru dengeye gelen abartılı bir yay efekti sağlar.

8f84142bff312373.gif

Bu eğrinin AnimatedSize ve TweenAnimationBuilder'e uygulandığını görmek için uygulamayı anında yeniden yükleyin.

206dd8d9c1fae95.gif

Yavaş animasyonları etkinleştirmek için DevTools'u kullanma

Flutter DevTools, animasyon efektlerinde hata ayıklama için uygulamanızdaki tüm animasyonları yavaşlatarak animasyonları daha net bir şekilde görmenizi sağlar.

DevTools'u açmak için uygulamanın hata ayıklama modunda çalıştığından emin olun ve VSCode'taki Hata ayıklama araç çubuğundan veya IntelliJ / Android Studio'daki Hata ayıklama aracı penceresinden Widget Denetleyici'yi seçerek Flutter DevTools'u aç düğmesini tıklayın.

3ce33dc01d096b14.png

363ae0fbcd0c2395.png

Widget denetleyicisi açıldığında araç çubuğundaki Animasyonları yavaşlat düğmesini tıklayın.

adea0a16d01127ad.png

5. Belirgin animasyon efektleri kullanma

Açık animasyonlar, örtülü animasyonlar gibi önceden oluşturulmuş animasyon efektleridir ancak hedef değer yerine parametre olarak bir Animation nesnesi alır. Bu, animasyonun halihazırda bir gezinme geçişi (ör. AnimatedSwitcher veya AnimationController) tarafından tanımlandığı durumlarda bu değerlerin yararlı olmasını sağlar.

Açık bir animasyon efekti kullanma

Belirli bir animasyon efektiyle başlamak için Card widget'ını AnimatedSwitcher ile sarın.

lib/question_screen.dart

class QuestionCard extends StatelessWidget {
  final String? question;

  const QuestionCard({
    required this.question,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return AnimatedSwitcher(                                 // NEW
      duration: const Duration(milliseconds: 300),           // NEW
      child: Card(
        key: ValueKey(question),
        elevation: 4,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text(
            question ?? '',
            style: Theme.of(context).textTheme.displaySmall,
          ),
        ),
      ),                                                     // NEW
    );
  }
}

AnimatedSwitcher varsayılan olarak bir geçiş efekti kullanır ancak transitionBuilder parametresini kullanarak bu ayarı geçersiz kılabilirsiniz. Geçiş oluşturucu, AnimatedSwitcher öğesine iletilen alt widget'ı ve bir Animation nesnesi sağlar. Bu, açık bir animasyon kullanmak için mükemmel bir fırsattır.

Bu codelab'de kullanacağımız ilk açık animasyon SlideTransition'dur. Bu animasyon, gelen ve giden widget'ların arasında hareket edeceği başlangıç ve bitiş ofsetini tanımlayan bir Animation<Offset> alır.

Ara geçişler, Animation değerini ara geçiş uygulanmış başka bir Animation değerine dönüştüren animate() yardımcı işlevine sahiptir. Bu, AnimatedSwitcher tarafından sağlanan Animation<double> öğesini SlideTransition widget'ına sağlanacak bir Animation<Offset> öğesine dönüştürmek için bir Tween<Offset> öğesinin kullanılabileceği anlamına gelir.

lib/question_screen.dart

class QuestionCard extends StatelessWidget {
  final String? question;

  const QuestionCard({
    required this.question,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return AnimatedSwitcher(
      transitionBuilder: (child, animation) {               // Add from here...
        final curveAnimation =
            CurveTween(curve: Curves.easeInCubic).animate(animation);
        final offsetAnimation =
            Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero)
                .animate(curveAnimation);
        return SlideTransition(position: offsetAnimation, child: child);
      },                                                    // To here.
      duration: const Duration(milliseconds: 300),
      child: Card(
        key: ValueKey(question),
        elevation: 4,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text(
            question ?? '',
            style: Theme.of(context).textTheme.displaySmall,
          ),
        ),
      ),
    );
  }
}

Bu işlemde, Animation'ye Curve uygulamak ve ardından 0,0 ile 1,0 arasında değişen bir Tween<double>'den x ekseninde -0,1 ile 0,0 arasında geçiş yapan bir Tween<Offset>'ye dönüştürmek için Tween.animate'nin kullanıldığını unutmayın.

Alternatif olarak, Animasyon sınıfında herhangi bir Tween (veya Animatable) değerini alıp yeni bir Animation değerine dönüştüren bir drive() işlevi vardır. Bu sayede tween'ler "zincirlenebilir" ve ortaya çıkan kod daha kısa olur:

lib/question_screen.dart

transitionBuilder: (child, animation) {
  var offsetAnimation = animation
      .drive(CurveTween(curve: Curves.easeInCubic))
      .drive(Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero));
  return SlideTransition(position: offsetAnimation, child: child);
},

Belirgin animasyonları kullanmanın bir diğer avantajı da kolayca bir araya getirilebilmeleridir. SlideTransition widget'ını sarmalayarak aynı kavisli animasyonu kullanan başka bir açık animasyon (FadeTransition) ekleyin.

lib/question_screen.dart

return AnimatedSwitcher(
  transitionBuilder: (child, animation) {
    final curveAnimation =
        CurveTween(curve: Curves.easeInCubic).animate(animation);
    final offsetAnimation =
        Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero)
            .animate(curveAnimation);
    final fadeInAnimation = curveAnimation;                            // NEW
    return FadeTransition(                                             // NEW
      opacity: fadeInAnimation,                                        // NEW
      child: SlideTransition(position: offsetAnimation, child: child), // NEW
    );                                                                 // NEW
  },

layoutBuilder'ı özelleştirme

AnimationSwitcher ile ilgili küçük bir sorun fark edebilirsiniz. QuestionCard yeni bir soruya geçtiğinde, animasyon çalışırken soruyu kullanılabilir alanın ortasına yerleştirir. Animasyon durdurulduğunda ise widget ekranın üst kısmına yapışır. Bu durum, soru kartının nihai konumu animasyon çalışırkenki konumla eşleşmediği için sarsıntılı bir animasyona neden olur.

d77de181bdde58f7.gif

Bu sorunu düzeltmek için AnimatedSwitcher'da, düzeni tanımlamak için kullanılabilecek bir layoutBuilder parametresi de vardır. Kartı ekranın üst kısmına hizalayacak şekilde düzen oluşturucuyu yapılandırmak için bu işlevi kullanın:

lib/question_screen.dart

@override
Widget build(BuildContext context) {
  return AnimatedSwitcher(
    layoutBuilder: (currentChild, previousChildren) {
      return Stack(
        alignment: Alignment.topCenter,
        children: <Widget>[
          ...previousChildren,
          if (currentChild != null) currentChild,
        ],
      );
    },

Bu kod, AnimatedSwitcher sınıfındaki defaultLayoutBuilder işlevinin değiştirilmiş bir sürümüdür ancak Alignment.center yerine Alignment.topCenter kullanılır.

Özet

  • Belirli animasyonlar, bir Animation nesnesi alan animasyon efektleridir (hedef değer ve süre alan ImplicitlyAnimatedWidgets'ın aksine).
  • Animation sınıfı, çalışan bir animasyonu temsil eder ancak belirli bir efekti tanımlamaz.
  • Bir animasyona Tween ve Eğri (CurveTween kullanarak) uygulamak için Tween().animate veya Animation.drive() işlevini kullanın.
  • AnimatedSwitcher'ın layoutBuilder parametresini kullanarak alt öğelerinin düzenini ayarlayın.

6. Animasyonun durumunu kontrol etme

Şimdiye kadar her animasyon, çerçeve tarafından otomatik olarak çalıştırıldı. Örtük animasyonlar otomatik olarak çalışır ve açık animasyon efektleri için animasyonların düzgün çalışması gerekir. Bu bölümde, AnimationController kullanarak kendi animasyon nesnelerinizi nasıl oluşturacağınızı ve Tween'leri birleştirmek için TweenSequence'i nasıl kullanacağınızı öğreneceksiniz.

AnimationController kullanarak animasyon çalıştırma

AnimationController kullanarak animasyon oluşturmak için aşağıdaki adımları uygulamanız gerekir:

  1. StatefulWidget oluşturma
  2. AnimationController'a Ticker sağlamak için State sınıfınızda SingleTickerProviderStateMixin mixin'ini kullanın
  3. initState yaşam döngüsü yönteminde AnimationController'ı başlatın. vsync (TickerProvider) parametresine mevcut State nesnesini sağlayın.
  4. AnimationController dinleyicilerini her bilgilendirdiğinde widget'ınızın yeniden oluşturulduğundan emin olmak için AnimatedBuilder'ı kullanın veya listen() ve setState'i manuel olarak çağırın.

flip_effect.dart adlı yeni bir dosya oluşturun ve aşağıdaki kodu kopyalayıp yapıştırın:

lib/flip_effect.dart

import 'dart:math' as math;

import 'package:flutter/widgets.dart';

class CardFlipEffect extends StatefulWidget {
  final Widget child;
  final Duration duration;

  const CardFlipEffect({
    super.key,
    required this.child,
    required this.duration,
  });

  @override
  State<CardFlipEffect> createState() => _CardFlipEffectState();
}

class _CardFlipEffectState extends State<CardFlipEffect>
    with SingleTickerProviderStateMixin {
  late final AnimationController _animationController;
  Widget? _previousChild;

  @override
  void initState() {
    super.initState();

    _animationController =
        AnimationController(vsync: this, duration: widget.duration);

    _animationController.addListener(() {
      if (_animationController.value == 1) {
        _animationController.reset();
      }
    });
  }

  @override
  void didUpdateWidget(covariant CardFlipEffect oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (widget.child.key != oldWidget.child.key) {
      _handleChildChanged(widget.child, oldWidget.child);
    }
  }

  void _handleChildChanged(Widget newChild, Widget previousChild) {
    _previousChild = previousChild;
    _animationController.forward();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animationController,
      builder: (context, child) {
        return Transform(
          alignment: Alignment.center,
          transform: Matrix4.identity()
            ..rotateX(_animationController.value * math.pi),
          child: _animationController.isAnimating
              ? _animationController.value < 0.5
                  ? _previousChild
                  : Transform.flip(flipY: true, child: child)
              : child,
        );
      },
      child: widget.child,
    );
  }
}

Bu sınıf bir AnimationController oluşturur ve çerçeve, widget yapılandırmasının değiştiğini ve yeni bir alt widget olabileceğini bildirmek için didUpdateWidget'ı çağrdığında animasyonu yeniden çalıştırır.

AnimatedBuilder, AnimationController dinleyicilerini her bilgilendirdiğinde widget ağacının yeniden oluşturulmasını sağlar. Transform widget'ı ise kartın çevrilmesini simüle etmek için 3D dönme efekti uygulamak üzere kullanılır.

Bu widget'ı kullanmak için her bir yanıt kartını bir CardFlipEffect widget'ı içine alın. Kart widget'ına bir key sağladığınızdan emin olun:

lib/question_screen.dart

@override
Widget build(BuildContext context) {
  return GridView.count(
    shrinkWrap: true,
    crossAxisCount: 2,
    childAspectRatio: 5 / 2,
    children: List.generate(answers.length, (index) {
      var color = Theme.of(context).colorScheme.primaryContainer;
      if (correctAnswer == index) {
        color = Theme.of(context).colorScheme.tertiaryContainer;
      }
      return CardFlipEffect(                                    // NEW
        duration: const Duration(milliseconds: 300),            // NEW
        child: Card.filled(                                     // NEW
          key: ValueKey(answers[index]),                        // NEW
          color: color,
          elevation: 2,
          margin: EdgeInsets.all(8),
          clipBehavior: Clip.hardEdge,
          child: InkWell(
            onTap: () => onTapped(index),
            child: Padding(
              padding: EdgeInsets.all(16.0),
              child: Center(
                child: Text(
                  answers.length > index ? answers[index] : '',
                  style: Theme.of(context).textTheme.titleMedium,
                  overflow: TextOverflow.clip,
                ),
              ),
            ),
          ),
        ),                                                      // NEW
      );
    }),
  );
}

Artık CardFlipEffect widget'ını kullanarak yanıt kartlarının çevrilmesini görmek için uygulamayı sıcak yeniden yükleyin.

5455def725b866f6.gif

Bu sınıfın, belirgin bir animasyon efekti gibi göründüğünü fark edebilirsiniz. Aslında, kendi sürümünüzü uygulamak için AnimatedWidget sınıfını doğrudan genişletmek genellikle iyi bir fikirdir. Maalesef bu sınıfın önceki widget'ı durumunda saklaması gerektiğinden StatefulWidget kullanması gerekiyor. Kendi uygunsuz animasyon efektlerinizi oluşturma hakkında daha fazla bilgi edinmek için AnimatedWidget API dokümanlarına bakın.

TweenSequence kullanarak gecikme ekleme

Bu bölümde, her kartın tek tek dönmesi için CardFlipEffect widget'ına bir gecikme eklersiniz. Başlamak için delayAmount adlı yeni bir alan ekleyin.

lib/flip_effect.dart

class CardFlipEffect extends StatefulWidget {
  final Widget child;
  final Duration duration;
  final double delayAmount;                      // NEW

  const CardFlipEffect({
    super.key,
    required this.child,
    required this.duration,
    required this.delayAmount,                   // NEW
  });

  @override
  State<CardFlipEffect> createState() => _CardFlipEffectState();
}

Ardından delayAmount öğesini AnswerCards derleme yöntemine ekleyin.

lib/question_screen.dart

@override
Widget build(BuildContext context) {
  return GridView.count(
    shrinkWrap: true,
    crossAxisCount: 2,
    childAspectRatio: 5 / 2,
    children: List.generate(answers.length, (index) {
      var color = Theme.of(context).colorScheme.primaryContainer;
      if (correctAnswer == index) {
        color = Theme.of(context).colorScheme.tertiaryContainer;
      }
      return CardFlipEffect(
        delayAmount: index.toDouble() / 2,                     // NEW
        duration: const Duration(milliseconds: 300),
        child: Card.filled(
          key: ValueKey(answers[index]),

Ardından _CardFlipEffectState içinde, TweenSequence kullanarak gecikmeyi uygulayan yeni bir animasyon oluşturun. Bu işlemin, dart:async kitaplığındaki Future.delayed gibi hiçbir yardımcı programı kullanıp kullanmadığını unutmayın. Bunun nedeni, gecikmenin animasyonun bir parçası olması ve widget'ın AnimationController'ı kullanırken açıkça kontrol etmediği bir şey olmasıdır. Aynı TickerProvider'ı kullandığından, DevTools'ta yavaş animasyonlar etkinleştirildiğinde animasyon efektinin hata ayıklanmasını kolaylaştırır.

TweenSequence kullanmak için iki TweenSequenceItem oluşturun. Bunlardan biri, animasyonu göreli bir süre boyunca 0 değerinde tutan bir ConstantTween, diğeri ise 0.0 ile 1.0 arasında değişen normal bir Tween içermelidir.

lib/flip_effect.dart

class _CardFlipEffectState extends State<CardFlipEffect>
    with SingleTickerProviderStateMixin {
  late final AnimationController _animationController;
  Widget? _previousChild;
  late final Animation<double> _animationWithDelay; // NEW

  @override
  void initState() {
    super.initState();

    _animationController = AnimationController(
        vsync: this, duration: widget.duration * (widget.delayAmount + 1));

    _animationController.addListener(() {
      if (_animationController.value == 1) {
        _animationController.reset();
      }
    });

    _animationWithDelay = TweenSequence<double>([   // NEW
      if (widget.delayAmount > 0)                   // NEW
        TweenSequenceItem(                          // NEW
          tween: ConstantTween<double>(0.0),        // NEW
          weight: widget.delayAmount,               // NEW
        ),                                          // NEW
      TweenSequenceItem(                            // NEW
        tween: Tween(begin: 0.0, end: 1.0),         // NEW
        weight: 1.0,                                // NEW
      ),                                            // NEW
    ]).animate(_animationController);               // NEW
  }

Son olarak, build yönteminde AnimationController'ın animasyonunu yeni gecikmeli animasyonla değiştirin.

lib/flip_effect.dart

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: _animationWithDelay,                            // Modify this line
    builder: (context, child) {
      return Transform(
        alignment: Alignment.center,
        transform: Matrix4.identity()
          ..rotateX(_animationWithDelay.value * math.pi),      // And this line
        child: _animationController.isAnimating
            ? _animationWithDelay.value < 0.5                  // And this one.
                ? _previousChild
                : Transform.flip(flipY: true, child: child)
            : child,
      );
    },
    child: widget.child,
  );
}

Şimdi uygulamayı anında yeniden yükleyin ve kartların tek tek açılmasını izleyin. Transform widget'ı tarafından sağlanan 3D efektin perspektifini değiştirmeyi deneyebilirsiniz.

28b5291de9b3f55f.gif

7. Özel gezinme geçişlerini kullanma

Şimdiye kadar efektlerin tek bir ekranda nasıl özelleştirileceğini gördük. Ancak animasyonları ekranlar arasında geçiş yapmak için de kullanabilirsiniz. Bu bölümde, yerleşik animasyon efektlerini ve pub.dev'deki resmi animations paketi tarafından sağlanan önceden oluşturulmuş animasyon efektlerini kullanarak ekran geçişlerine animasyon efektleri nasıl uygulayacağınızı öğreneceksiniz.

Gezinme geçişine animasyon ekleme

PageRouteBuilder sınıfı, geçiş animasyonunu özelleştirmenize olanak tanıyan bir Route sınıfıdır. Bu, Navigator tarafından çalıştırılan gelen ve giden animasyonu temsil eden iki Animation nesnesi sağlayan transitionBuilder geri çağırma işlevini geçersiz kılmanıza olanak tanır.

Geçiş animasyonunu özelleştirmek için MaterialPageRoute öğesini PageRouteBuilder ile değiştirin. Kullanıcı HomeScreen öğesinden QuestionScreen öğesine gittiğinde geçiş animasyonunu özelleştirmek için de MaterialPageRoute öğesini PageRouteBuilder ile değiştirin. Yeni ekranın önceki ekranın üzerine yavaşça gelmesini sağlamak için FadeTransition (açıkça animasyonlu bir widget) kullanın.

lib/home_screen.dart

ElevatedButton(
  onPressed: () {
    // Show the question screen to start the game
    Navigator.push(
      context,
      PageRouteBuilder(                                         // NEW
        pageBuilder: (context, animation, secondaryAnimation) { // NEW
          return QuestionScreen();                              // NEW
        },                                                      // NEW
        transitionsBuilder:                                     // NEW
            (context, animation, secondaryAnimation, child) {   // NEW
          return FadeTransition(                                // NEW
            opacity: animation,                                 // NEW
            child: child,                                       // NEW
          );                                                    // NEW
        },                                                      // NEW
      ),                                                        // NEW
    );
  },
  child: Text('New Game'),
),

Animasyon paketi, FadeThroughTransition gibi önceden oluşturulmuş animasyon efektleri sunar. Animasyon paketini içe aktarın ve FadeTransition öğesini FadeThroughTransition widget'ı ile değiştirin:

lib/home_screen.dart

import 'package;animations/animations.dart';

ElevatedButton(
  onPressed: () {
    // Show the question screen to start the game
    Navigator.push(
      context,
      PageRouteBuilder(
        pageBuilder: (context, animation, secondaryAnimation) {
          return const QuestionScreen();
        },
        transitionsBuilder:
            (context, animation, secondaryAnimation, child) {
          return FadeThroughTransition(                          // NEW
            animation: animation,                                // NEW
            secondaryAnimation: secondaryAnimation,              // NEW
            child: child,                                        // NEW
          );                                                     // NEW
        },
      ),
    );
  },
  child: Text('New Game'),
),

Tahmine dayalı geri hareketi animasyonunu özelleştirme

1c0558ffa3b76439.gif

Tahmini geri, kullanıcının gezinmeden önce mevcut rotanın veya uygulamanın arkasında ne olduğunu görmek için bakmasına olanak tanıyan yeni bir Android özelliğidir. Bir bakışta animasyonu, kullanıcı ekranı geri sürüklerken parmağının konumuna göre belirlenir.

Flutter, gezinme yığınında açılacak rotaları olmadığında veya başka bir deyişle, geri tuşuna basıldığında uygulamadan çıkıldığında bu özelliği sistem düzeyinde etkinleştirerek sistem tahmini geri özelliğini destekler. Bu animasyon Flutter tarafından değil, sistem tarafından yönetilir.

Flutter, Flutter uygulamasındaki rotalar arasında gezinirken tahmini geri özelliğini de destekler. PredictiveBackPageTransitionsBuilder adlı özel bir PageTransitionsBuilder, sistem tahmini geri hareketlerini dinler ve sayfa geçişini hareketin ilerleme hızına göre yönlendirir.

Tahmini geri düğmesi yalnızca Android U ve sonraki sürümlerde desteklenir ancak Flutter, orijinal geri hareketi davranışına ve ZoomPageTransitionBuilder'a sorunsuz bir şekilde geri döner. Kendi uygulamanızda nasıl ayarlayacağınızla ilgili bir bölüm de dahil olmak üzere daha fazla bilgi için blog yayınımızı inceleyin.

Uygulamanızın ThemeData yapılandırmasında PageTransitionsTheme'i, Android'de PredictiveBack'i ve diğer platformlarda animations paketindeki geçiş efektinin solma özelliğini kullanacak şekilde yapılandırın:

lib/main.dart

import 'package:animations/animations.dart';                                 // NEW
import 'package:flutter/material.dart';

import 'home_screen.dart';

void main() {
  runApp(MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
        pageTransitionsTheme: PageTransitionsTheme(
          builders: {
            TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),  // NEW
            TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(),         // NEW
            TargetPlatform.macOS: FadeThroughPageTransitionsBuilder(),       // NEW
            TargetPlatform.windows: FadeThroughPageTransitionsBuilder(),     // NEW
            TargetPlatform.linux: FadeThroughPageTransitionsBuilder(),       // NEW
          },
        ),
      ),
      home: HomeScreen(),
    );
  }
}

Artık Navigator.push() geri çağırma işlevini MaterialPageRoute olarak değiştirebilirsiniz.

lib/home_screen.dart

ElevatedButton(
  onPressed: () {
    // Show the question screen to start the game
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) {       // NEW
        return const QuestionScreen();             // NEW
      }),                                          // NEW
    );
  },
  child: Text('New Game'),
),

Mevcut soruyu değiştirmek için FadeThroughTransition'ı kullanma

AnimatedSwitcher widget'ı, oluşturucu geri çağırma işlevinde yalnızca bir animasyon sağlar. Bu sorunu gidermek için animations paketi bir PageTransitionSwitcher sağlar.

lib/question_screen.dart

class QuestionCard extends StatelessWidget {
  final String? question;

  const QuestionCard({
    required this.question,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return PageTransitionSwitcher(                                          // NEW
      layoutBuilder: (entries) {                                            // NEW
        return Stack(                                                       // NEW
          alignment: Alignment.topCenter,                                   // NEW
          children: entries,                                                // NEW
        );                                                                  // NEW
      },                                                                    // NEW
      transitionBuilder: (child, animation, secondaryAnimation) {           // NEW
        return FadeThroughTransition(                                       // NEW
          animation: animation,                                             // NEW
          secondaryAnimation: secondaryAnimation,                           // NEW
          child: child,                                                     // NEW
        );                                                                  // NEW
      },                                                                    // NEW
      duration: const Duration(milliseconds: 300),
      child: Card(
        key: ValueKey(question),
        elevation: 4,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text(
            question ?? '',
            style: Theme.of(context).textTheme.displaySmall,
          ),
        ),
      ),
    );
  }
}

OpenContainer'ı kullanma

77358e5776eb104c.png

animations paketindeki OpenContainer widget'ı, iki widget arasında görsel bir bağlantı oluşturmak için genişleyen bir kapsayıcı dönüştürme animasyon efekti sağlar.

Başlangıçta closedBuilder tarafından döndürülen widget gösterilir ve kapsayıcıya dokunulduğunda veya openContainer geri çağırma işlevi çağrıldığında openBuilder tarafından döndürülen widget'a genişler.

openContainer geri çağırma işlevini görünüm modeline bağlamak için QuestionCard widget'ına yeni bir viewModel geçişi ekleyin ve "Oyun Bitti" ekranını göstermek için kullanılacak bir geri çağırma işlevi depolayın:

lib/question_screen.dart

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

  @override
  State<QuestionScreen> createState() => _QuestionScreenState();
}

class _QuestionScreenState extends State<QuestionScreen> {
  late final QuizViewModel viewModel =
      QuizViewModel(onGameOver: _handleGameOver);
  VoidCallback? _showGameOverScreen;                                    // NEW

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: viewModel,
      builder: (context, child) {
        return Scaffold(
          appBar: AppBar(
            actions: [
              TextButton(
                onPressed:
                    viewModel.hasNextQuestion && viewModel.didAnswerQuestion
                        ? () {
                            viewModel.getNextQuestion();
                          }
                        : null,
                child: const Text('Next'),
              )
            ],
          ),
          body: Center(
            child: Column(
              children: [
                QuestionCard(                                           // NEW
                  onChangeOpenContainer: _handleChangeOpenContainer,    // NEW
                  question: viewModel.currentQuestion?.question,        // NEW
                  viewModel: viewModel,                                 // NEW
                ),                                                      // NEW
                Spacer(),
                AnswerCards(
                  onTapped: (index) {
                    viewModel.checkAnswer(index);
                  },
                  answers: viewModel.currentQuestion?.possibleAnswers ?? [],
                  correctAnswer: viewModel.didAnswerQuestion
                      ? viewModel.currentQuestion?.correctAnswer
                      : null,
                ),
                StatusBar(viewModel: viewModel),
              ],
            ),
          ),
        );
      },
    );
  }

  void _handleChangeOpenContainer(VoidCallback openContainer) {        // NEW
    _showGameOverScreen = openContainer;                               // NEW
  }                                                                    // NEW

  void _handleGameOver() {                                             // NEW
    if (_showGameOverScreen != null) {                                 // NEW
      _showGameOverScreen!();                                          // NEW
    }                                                                  // NEW
  }                                                                    // NEW
}

GameOverScreen adlı yeni bir widget ekleyin:

lib/question_screen.dart

class GameOverScreen extends StatelessWidget {
  final QuizViewModel viewModel;
  const GameOverScreen({required this.viewModel, super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Scoreboard(
              score: viewModel.score,
              totalQuestions: viewModel.totalQuestions,
            ),
            Text(
              'You Win!',
              style: Theme.of(context).textTheme.displayLarge,
            ),
            Text(
              'Score: ${viewModel.score} / ${viewModel.totalQuestions}',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            ElevatedButton(
              child: Text('OK'),
              onPressed: () {
                Navigator.popUntil(context, (route) => route.isFirst);
              },
            ),
          ],
        ),
      ),
    );
  }
}

QuestionCard widget'ında, Card öğesini animations paketindeki OpenContainer widget'ıyla değiştirin. viewModel ve open container geri çağırma işlevi için iki yeni alan ekleyin:

lib/question_screen.dart

class QuestionCard extends StatelessWidget {
  final String? question;

  const QuestionCard({
    required this.onChangeOpenContainer,
    required this.question,
    required this.viewModel,
    super.key,
  });

  final ValueChanged<VoidCallback> onChangeOpenContainer;
  final QuizViewModel viewModel;

  static const _backgroundColor = Color(0xfff2f3fa);

  @override
  Widget build(BuildContext context) {
    return PageTransitionSwitcher(
      duration: const Duration(milliseconds: 200),
      transitionBuilder: (child, animation, secondaryAnimation) {
        return FadeThroughTransition(
          animation: animation,
          secondaryAnimation: secondaryAnimation,
          child: child,
        );
      },
      child: OpenContainer(                                         // NEW
        key: ValueKey(question),                                    // NEW
        tappable: false,                                            // NEW
        closedColor: _backgroundColor,                              // NEW
        closedShape: const RoundedRectangleBorder(                  // NEW
          borderRadius: BorderRadius.all(Radius.circular(12.0)),    // NEW
        ),                                                          // NEW
        closedElevation: 4,                                         // NEW
        closedBuilder: (context, openContainer) {                   // NEW
          onChangeOpenContainer(openContainer);                     // NEW
          return ColoredBox(                                        // NEW
            color: _backgroundColor,                                // NEW
            child: Padding(                                         // NEW
              padding: const EdgeInsets.all(16.0),                  // NEW
              child: Text(
                question ?? '',
                style: Theme.of(context).textTheme.displaySmall,
              ),
            ),
          );
        },
        openBuilder: (context, closeContainer) {                    // NEW
          return GameOverScreen(viewModel: viewModel);              // NEW
        },                                                          // NEW
      ),
    );
  }
}

4120f9395857d218.gif

8. Tebrikler

Tebrikler, bir Flutter uygulamasına animasyon efektleri eklemeyi başardınız ve Flutter'ın animasyon sisteminin temel bileşenleri hakkında bilgi edindiniz. Özellikle şunları öğrendiniz:

  • ImplicitlyAnimatedWidget nasıl kullanılır?
  • ExplicitlyAnimatedWidget nasıl kullanılır?
  • Eğriler ve ara animasyonlar animasyonlara nasıl uygulanır?
  • AnimatedSwitcher veya PageRouteBuilder gibi önceden oluşturulmuş geçiş widget'larını kullanma
  • animations paketindeki FadeThroughTransition ve OpenContainer gibi önceden oluşturulmuş animasyon efektlerini kullanma
  • Android'de Tahmini Geri özelliği için destek ekleme de dahil olmak üzere varsayılan geçiş animasyonunu özelleştirme.

3026390ad413769c.gif

Sırada ne var?

Aşağıdaki codelab'lerden bazılarına göz atın:

Alternatif olarak, çeşitli animasyon tekniklerini gösteren animasyonlar örnek uygulamasını indirebilirsiniz.

Daha fazla bilgi

flutter.dev'de animasyonlarla ilgili daha fazla kaynak bulabilirsiniz:

Dilerseniz Medium'daki şu makalelere de göz atabilirsiniz:

Referans dokümanları