1. Giriş
Animasyonlar, uygulamanızın kullanıcı deneyimini iyileştirmenin, kullanıcıya önemli bilgileri iletmenin ve uygulamanızı daha iyi hale getirip kullanımını daha keyifli hale getirmenin harika 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 bestelemeyi kolaylaştırmak için önceden oluşturulmuş animasyon efektleri ve diğer API'ler sunar.
- Örtülü animasyonlar, animasyonun tamamını otomatik olarak çalıştıran önceden oluşturulmuş animasyon efektleridir. Animasyonun hedef değeri değiştiğinde animasyonu geçerli değerden hedef değere kadar çalıştırır ve aradaki her değeri gösterir. Böylece widget sorunsuz bir şekilde animasyon yapar. Örtülü animasyonlara örnek olarak
AnimatedSize,AnimatedScaleveAnimatedPositionedverilebilir. - Açık animasyonlar da önceden oluşturulmuş animasyon efektleridir ancak çalışmak için
Animationnesnesi gerektirir. Örnek olarakSizeTransition,ScaleTransitionveyaPositionedTransitionverilebilir. - 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 değerlerinden oluşur.
Listenablesınıfının bir alt sınıfıdır ve animasyon çalışırken durum değiştiğinde dinleyicilerini bilgilendirir. - AnimationController, Animation oluşturmanın ve durumunu kontrol etmenin bir yoludur.
forward(),reset(),stop()verepeat()gibi yöntemleri, ölçek, boyut veya konum gibi görüntülenen animasyon efektini tanımlamaya gerek kalmadan animasyonu kontrol etmek için kullanılabilir. - Ara değerler, başlangıç ve bitiş değerleri arasındaki değerleri ara değer olarak kullanmak için kullanılır ve çift,
OffsetveyaColorgibi 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şlangıcında veya sonunda değişim hızını daha hızlı ya da daha yavaş hale getirmek için genellikle bir yumuşak geçiş eğrisi uygulanı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.
Ne oluşturacaksınız?
Bu codelab'de, çeşitli animasyon efektleri ve teknikleri içeren çoktan seçmeli bir bilgi yarışması oyunu oluşturacaksınız.

Öğreneceğiniz işlemler:
- Boyutunu ve rengini canlandıran bir widget oluşturma
- 3D kart çevirme efekti oluşturma
- Animasyon paketindeki hazır animasyon efektlerini kullanma
- Android'in en yeni sürümünde bulunan tahmin edilen geri gitme hareketi desteğini ekleyin.
Neler öğreneceksiniz?
Bu codelab'de öğrenecekleriniz:
- Çok fazla kod yazmadan harika görünümlü animasyonlar elde etmek için örtülü animasyonlu efektleri kullanma
AnimatedSwitcherveyaAnimationControllergibi ö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
AnimationControllernasıl kullanılır? - Minimal kurulumla süslü animasyon efektleri görüntülemek için
animationspaketini kullanma
İhtiyacınız olanlar
- Flutter SDK'sı
- VSCode veya Android Studio / IntelliJ gibi bir IDE
2. Flutter geliştirme ortamınızı kurma
Bu laboratuvarı tamamlamak için iki yazılıma ihtiyacınız var: Flutter SDK ve bir düzenleyici.
Codelab'i aşağıdaki cihazlardan herhangi birini kullanarak çalıştırabilirsiniz:
- Bilgisayarınıza bağlı ve geliştirici moduna ayarlanmış fiziksel bir Android (7. adımda tahmin edilen geri gitme özelliğini uygulamak için önerilir) veya iOS cihaz.
- iOS simülatörü (Xcode araçlarının yüklenmesi gerekir).
- Android Emulator (Android Studio'da kurulum gerektirir).
- Tarayıcı (hata ayıklama için Chrome gereklidir).
- Windows, Linux veya macOS işletim sistemli bir masaüstü bilgisayar. Dağıtmayı planladığınız platformda geliştirme yapmanız gerekir. Bu nedenle, bir 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 ayrıntılı olarak ele alınan işletim sistemine özgü gereksinimler vardır.
Yüklemenizi doğrulama
Flutter SDK'nızın doğru şekilde yapılandırıldığını ve yukarıdaki hedef platformlardan en az birinin yüklendiğini doğrulamak 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şlangıç uygulamasını indirme
Başlangıç uygulamasını GitHub'daki flutter/samples deposundan kopyalamak için git komutunu 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 hedef cihazı (ör. android, ios veya chrome) belirtin. Desteklenen platformların tam listesi için Desteklenen platformlar sayfasına bakın.
flutter run -d android
Uygulamayı istediğiniz IDE'yi kullanarak da çalıştırabilir ve hatalarını ayıklayabilirsiniz. Daha fazla bilgi için resmi Flutter belgelerini inceleyin.
Kodu inceleme
Başlangıç uygulaması, model-view-view-model (MVVM) tasarım kalıbını izleyen iki ekrandan oluşan çoktan seçmeli bir bilgi yarışması oyunudur. QuestionScreen (Görünüm), QuizViewModel (Görünüm Modeli) sınıfını kullanarak kullanıcıya QuestionBank (Model) sınıfından çoktan seçmeli sorular sorar.
- home_screen.dart: Yeni Oyun düğmesinin bulunduğu bir ekranı gösterir.
- main.dart:
MaterialAppöğesini Material 3'ü kullanacak ve ana ekranı gösterecek şekilde yapılandırır. - model.dart: Uygulamada 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: Test oyununun durumunu ve mantığını saklar.
QuestionScreentarafından görüntülenir.

Uygulama, 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şi dışında henüz herhangi bir animasyonlu efekti desteklemiyor.
4. Örtülü animasyon efektlerini kullanma
Örtülü animasyonlar herhangi bir özel yapılandırma gerektirmediğinden birçok durumda iyi bir seçimdir. Bu bölümde, StatusBar widget'ını güncelleyerek animasyonlu bir skor tablosu göstermesini sağlayacaksınız. Sık kullanılan örtülü animasyon efektlerini bulmak için ImplicitlyAnimatedWidget API dokümanlarına göz atın.

Animasyon içermeyen skor tablosu widget'ını oluşturma
Aşağıdaki kodu kullanarak yeni bir lib/scoreboard.dart dosyası 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, daha önce puanı ve toplam soru sayısını gösteren Text widget'larının yerine StatusBar widget'ının alt öğelerine Scoreboard widget'ını ekleyin. Düzenleyiciniz, dosyanın en üstüne gerekli import "scoreboard.dart" öğesini otomatik olarak eklemelidir.
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, her soru için bir yıldız simgesi gösterir. Bir soru doğru yanıtlandığında başka bir yıldız animasyon olmadan anında yanar. Sonraki adımlarda, kullanıcının puanının değiştiğini bildirmek için puanın boyutunu ve rengini animasyonla değiştireceksiniz.
Örtülü animasyon efekti kullanma
Yıldız etkinleştiğinde scale tutarını 0.5 değerinden 1.0 değerine değiştirmek için AnimatedScale bileşenini kullanan AnimatedStar adlı yeni bir bileşen 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(isActive: score > i), // Edit this line.
],
),
);
}
}
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'ı, örtülü bir animasyon kullanarak boyutunu güncelliyor. Burada Icon'nın color animasyonu yok. Yalnızca AnimatedScale widget'ı tarafından yapılan scale animasyonu var.

İki değer arasında interpolasyon yapmak için Tween kullanma
AnimatedStar widget'ının renginin, isActive alanı doğru olarak değiştirildikten hemen sonra değiştiğine dikkat edin.
Animasyonlu bir renk efekti elde etmek için AnimatedContainer widget'ını (ImplicitlyAnimatedWidget'ın başka bir alt sınıfı) kullanmayı deneyebilirsiniz. Bu widget, renk de dahil olmak üzere tüm özelliklerini otomatik olarak canlandırabilir. Maalesef widget'ımızda kapsayıcı değil simge gösterilmesi gerekiyor.
Ayrıca, simgelerin şekilleri arasında geçiş efektleri uygulayan AnimatedIcon'ı da deneyebilirsiniz. Ancak AnimatedIcons sınıfında yıldız simgesinin varsayılan bir uygulaması yoktur.
Bunun yerine, parametre olarak Tween alan TweenAnimationBuilder adlı başka bir ImplicitlyAnimatedWidget alt sınıfını kullanacağız. Tween, iki değer (begin ve end) alan ve aradaki değerleri hesaplayan bir sınıftır. Böylece animasyon bu değerleri gösterebilir. Bu örnekte, animasyon efektimizi oluşturmak için gereken Tween arayüzünü karşılayan bir ColorTween kullanacağız.
Icon widget'ını seçin ve IDE'nizdeki "Wrap with Builder" hızlı işlemini kullanarak adı TweenAnimationBuilder olarak değiştirin. Ardından süreyi ve ColorTween'i belirtin.
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); // And modify this line.
},
),
);
}
}
Yeni animasyonu görmek için uygulamayı anında yeniden yükleyin.

isActive parametresinin değerine bağlı olarak ColorTween değerinin end olarak değiştiğini unutmayın. Bunun nedeni, TweenAnimationBuilder değer her değiştiğinde animasyonunu yeniden çalıştırmasıdır.Tween.end Bu durumda, yeni animasyon mevcut animasyon değerinden yeni bitiş değerine kadar çalışır. Böylece, animasyon çalışırken bile rengi istediğiniz zaman değiştirebilir ve doğru ara değerlerle sorunsuz bir animasyon efekti görüntüleyebilirsiniz.
Eğri uygulama
Bu animasyon efektlerinin her ikisi de sabit bir hızda çalışır ancak animasyonlar hızlandığında veya yavaşladığında genellikle daha ilgi çekici ve bilgilendirici olur.
Curve, bir parametrenin zaman içindeki değişim hızını tanımlayan bir yumuşak geçiş işlevi uygular. Flutter, Curves sınıfında easeIn veya easeOut gibi önceden oluşturulmuş bir dizi yumuşatma eğrisiyle birlikte gelir.


Curves API dokümanları sayfasında bulunan bu diyagramlar, eğrilerin nasıl çalıştığı hakkında fikir verir. Eğriler, 0,0 ile 1,0 arasındaki bir giriş değerini (x ekseninde gösterilir) 0,0 ile 1,0 arasındaki bir çıkış değerine (y ekseninde gösterilir) dönüştürür. Bu diyagramlarda, çeşitli animasyon efektlerinin yumuşatma eğrisi kullanıldığında nasıl göründüğüne dair bir önizleme de gösterilir.
AnimatedStar'da _curve adlı yeni bir alan oluşturun ve bunu AnimatedScale ile 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, yay hareketiyle başlayıp sona doğru dengelenen abartılı bir yay efekti sağlar.

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

Yavaş animasyonları etkinleştirmek için Geliştirici Araçları'nı kullanma
Flutter DevTools, animasyon efektlerinde hata ayıklamak için uygulamanızdaki tüm animasyonları yavaşlatma olanağı sunar. Böylece animasyonu daha net görebilirsiniz.
DevTools'u açmak için uygulamanın hata ayıklama modunda çalıştığından emin olun ve VSCode'daki Debug toolbar'da Widget Inspector'ı seçerek veya IntelliJ / Android Studio'daki Debug araç penceresi'da Open Flutter DevTools düğmesini seçerek Widget Inspector'ı açın.


Widget inceleyici açıldıktan sonra araç çubuğunda Animasyonları yavaşlat düğmesini tıklayın.

5. Açık animasyon efektleri kullanma
Açık animasyonlar, örtülü animasyonlar gibi önceden oluşturulmuş animasyon efektleridir ancak hedef değer almak yerine parametre olarak bir Animation nesnesi alırlar. Bu nedenle, animasyonun zaten bir gezinme geçişi (ör. AnimatedSwitcher veya AnimationController) ile tanımlandığı durumlarda kullanışlıdır.
Açık bir animasyon efekti kullanma
Açık bir animasyon efektiyle çalışmaya başlamak için Card widget'ını AnimatedSwitcher ile sarmalayı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 çapraz geçiş efektini 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 harika bir fırsattır.
Bu codelab'de kullanacağımız ilk açık animasyon, gelen ve giden widget'ların arasında hareket edeceği başlangıç ve bitiş uzaklığını tanımlayan bir SlideTransition alır.Animation<Offset>
Ara değerler, animate() yardımcı işlevine sahiptir. Bu işlev, herhangi bir Animation değerini ara değer uygulanmış başka bir Animation değerine dönüştürür. Bu, AnimatedSwitcher tarafından sağlanan Animation öğesinin Tween kullanılarak SlideTransition widget'ına sağlanacak bir Animation öğesine dönüştürülebileceğ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şlemin, Tween.animate kullanarak Animation öğesine Curve uyguladığını ve ardından 0,0 ile 1,0 arasında değişen bir Tween değerini x ekseninde -0,1 ile 0,0 arasında geçiş yapan bir Tween değerine dönüştürdüğünü unutmayın.
Alternatif olarak, Animation sınıfında herhangi bir drive() fonksiyonu bulunur. Bu fonksiyon, herhangi bir Tween (veya Animatable) değerini alır ve yeni bir Animation değerine dönüştürür. Bu sayede ara değerler "zincirlenebilir" ve sonuçta elde edilen 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);
},
Açık animasyon kullanmanın bir diğer avantajı da bunların birlikte oluşturulabilmesidir. FadeTransition widget'ını sarmalayarak aynı eğri animasyonu kullanan başka bir açık animasyon FadeTransition ekleyin.SlideTransition
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. Bir QuestionCard yeni bir soruya geçtiğinde, animasyon çalışırken soruyu mevcut alanın ortasına yerleştirir. Ancak animasyon durdurulduğunda widget ekranın üst kısmına sabitlenir. Bu durum, soru kartının son konumu animasyon çalışırkenki konumuyla eşleşmediği için animasyonun titremesine neden olur.

Bu sorunu düzeltmek için AnimatedSwitcher, düzeni tanımlamak üzere kullanılabilecek bir layoutBuilder parametresine de sahiptir. Kartı ekranın üst kısmına hizalamak için düzen oluşturucuyu yapılandırmak üzere 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'ın değiştirilmiş bir sürümüdür ancak Alignment.center yerine Alignment.topCenter kullanır.
Özet
- Açık animasyonlar, bir
Animationnesnesi alan animasyon efektleridir (ImplicitlyAnimatedWidgetsise hedefvaluevedurationalır). Animationsınıfı, çalışan bir animasyonu temsil eder ancak belirli bir efekti tanımlamaz.TweensveCurves(CurveTweenkullanılarak) uygulamak içinTween().animateveyaAnimation.drive()'ı kullanın.- Alt öğelerini nasıl yerleştireceğini ayarlamak için
AnimatedSwitcheröğesininlayoutBuilderparametresini kullanın.
6. Animasyonun durumunu kontrol etme
Şimdiye kadar her animasyon, çerçeve tarafından otomatik olarak çalıştırıldı. Örtülü animasyonlar otomatik olarak çalışır ve açık animasyon efektlerinin doğru çalışması için Animation gerekir. Bu bölümde, AnimationController kullanarak kendi Animation nesnelerinizi nasıl oluşturacağınızı ve Tween'leri birleştirmek için TweenSequence'yi nasıl kullanacağınızı öğreneceksiniz.
AnimationController kullanarak animasyon çalıştırma
AnimationController kullanarak animasyon oluşturmak için şu adımları uygulamanız gerekir:
StatefulWidgetoluşturunAnimationControlleriçinTickersağlamak üzereStatesınıfınızdaSingleTickerProviderStateMixinmixin'ini kullanınAnimationControlleröğesiniinitStateyaşam döngüsü yönteminde başlatın ve geçerliStatenesnesinivsync(TickerProvider) parametresine sağlayın.AnimationController, dinleyicilerini her bildirdiğinde (AnimatedBuilderkullanılarak veyalisten()vesetStatemanuel olarak çağrılarak) widget'ınızın yeniden oluşturulduğundan emin olun.
Yeni bir dosya (flip_effect.dart) 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, AnimationController oluşturur ve widget yapılandırmasının değiştiğini ve yeni bir alt widget olabileceğini bildirmek için çerçeve didUpdateWidget işlevini her ç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 ters çevrilmesini simüle etmek için 3D döndürme efekti uygulamak üzere kullanılır.
Bu widget'ı kullanmak için her yanıt kartını CardFlipEffect widget'ı ile sarmalayın. Card 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
);
}),
);
}
Şimdi, CardFlipEffect widget'ını kullanarak yanıt kartlarının çevrildiğini görmek için uygulamayı anında yeniden yükleyin.

Bu sınıfın, açık bir animasyon efektine çok benzediğini fark edebilirsiniz. Hatta kendi sürümünüzü uygulamak için genellikle AnimatedWidget sınıfını doğrudan genişletmeniz iyi bir fikirdir. Maalesef bu sınıfın önceki widget'ı State içinde saklaması gerektiğinden StatefulWidget kullanması gerekir. Kendi açık animasyon efektlerinizi oluşturma hakkında daha fazla bilgi edinmek için AnimatedWidget ile ilgili API dokümanlarına bakın.
TweenSequence kullanarak gecikme ekleme
Bu bölümde, her kartın tek tek çevrilmesi için CardFlipEffect widget'ına gecikme ekleyeceksiniz. 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 oluşturma 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 Animation oluşturun. Bu kodun, dart:async kitaplığındaki Future.delayed gibi yardımcı programları kullanmadığını unutmayın. Bunun nedeni, gecikmenin animasyonun bir parçası olması ve widget'ın AnimationController kullanırken açıkça kontrol ettiği bir şey olmamasıdır. Bu, Geliştirici Araçları'nda yavaş animasyonlar etkinleştirildiğinde animasyon efektinin hata ayıklamasını kolaylaştırır.TickerProvider
TweenSequence kullanmak için iki TweenSequenceItem oluşturun. Bunlardan biri, animasyonu göreceli bir süre boyunca 0'da tutan bir ConstantTween, diğeri ise 0.0'den 1.0'a giden 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>([ // Add from here...
if (widget.delayAmount > 0)
TweenSequenceItem(
tween: ConstantTween<double>(0.0),
weight: widget.delayAmount,
),
TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.0), weight: 1.0),
]).animate(_animationController); // To here.
}
Son olarak, AnimationController animasyonunu build yöntemindeki 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 çevrilmesini izleyin. Bir meydan okuma için Transform widget'ı tarafından sağlanan 3D efektin perspektifini değiştirmeyi deneyin.

7. Özel gezinme geçişlerini kullanma
Şimdiye kadar efektleri tek bir ekranda nasıl özelleştirebileceğimizi gördük. Animasyonları kullanmanın bir başka yolu da ekranlar arasında geçiş yapmak için kullanmaktır. Bu bölümde, animations paketinin pub.dev'de sunduğu yerleşik animasyon efektlerini ve önceden oluşturulmuş animasyon efektlerini kullanarak ekran geçişlerine animasyon efektleri uygulamayı öğreneceksiniz.
Gezinme geçişini canlandırma
PageRouteBuilder sınıfı, geçiş animasyonunu özelleştirmenize olanak tanıyan bir Route'dır. Bu yöntem, Navigator tarafından çalıştırılan gelen ve giden animasyonu temsil eden iki Animation nesnesi sağlayan transitionBuilder geri çağırmasını geçersiz kılmanıza olanak tanır.
Geçiş animasyonunu özelleştirmek için MaterialPageRoute yerine PageRouteBuilder, kullanıcı HomeScreen öğesinden QuestionScreen öğesine gittiğinde geçiş animasyonunu özelleştirmek için ise HomeScreen yerine QuestionScreen yazın. Yeni ekranın önceki ekranın üzerinde görünmesini 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( // Add from here...
pageBuilder: (context, animation, secondaryAnimation) {
return const QuestionScreen();
},
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
), // To here.
);
},
child: Text('New Game'),
),
Animasyon paketi, FadeThroughTransition gibi hazır animasyon efektleri sunar. Animasyon paketini içe aktarın ve FadeTransition widget'ını FadeThroughTransition widget'ıyla 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( // Add from here...
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
); // To here.
},
),
);
},
child: Text('New Game'),
),
Tahmin edilen geri gitme animasyonunu özelleştirme

Tahmin edilen geri gitme, kullanıcının gezinmeden önce mevcut rota veya uygulamanın arkasına bakarak ne olduğunu görmesini sağlayan yeni bir Android özelliğidir. Göz atma animasyonu, kullanıcının parmağını ekranda geriye doğru sürüklerken parmağının konumuna göre belirlenir.
Flutter, gezinme yığınında pop edilecek rota olmadığında veya başka bir deyişle geri tuşuna basıldığında uygulamadan çıkılacaksa özelliği sistem düzeyinde etkinleştirerek sistemin tahmin edilen geri gitme özelliğini destekler. Bu animasyon, Flutter'ın kendisi tarafından değil, sistem tarafından işlenir.
Flutter, bir Flutter uygulamasında rotalar arasında gezinirken tahmin edilen geri gitme özelliğini de destekler. PageTransitionsBuilder adlı özel bir PredictiveBackPageTransitionsBuilder, sistemin tahmin edilen geri gitme hareketlerini dinler ve sayfa geçişini hareketin ilerleme durumuyla yönlendirir.
Tahmin edilen geri gitme özelliği 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 bölüm de dahil olmak üzere daha fazla bilgi için blog yayınımıza göz atın.
Uygulamanızın ThemeData yapılandırmasında, PageTransitionsTheme öğesini Android'de PredictiveBack, diğer platformlarda ise animations paketindeki geçiş efekti 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),
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 aramayı MaterialPageRoute olarak değiştirebilirsiniz.
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
MaterialPageRoute( // Add from here...
builder: (context) {
return const QuestionScreen();
},
), // To here.
);
},
child: Text('New Game'),
),
Geçerli soruyu değiştirmek için FadeThroughTransition'ı kullanın.
AnimatedSwitcher widget'ı, oluşturucu geri çağırmasında yalnızca bir Animation sağlar. Bu sorunu gidermek için animations paketi 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( // Add from here...
layoutBuilder: (entries) {
return Stack(alignment: Alignment.topCenter, children: entries);
},
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
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,
),
),
),
);
}
}
OpenContainer'ı kullanma

animations paketindeki OpenContainer widget'ı, iki widget arasında görsel bağlantı oluşturmak için genişleyen bir kapsayıcı dönüşümü animasyon efekti sağlar.
Başlangıçta closedBuilder tarafından döndürülen widget gösterilir. 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şletilir.
openContainer geri çağırmasını görünüm modeline bağlamak için QuestionCard widget'ına yeni bir geçiş ekleyin ve "Oyun Bitti" ekranını göstermek için kullanılacak bir geri çağırma depolayın:viewModel
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
}
Yeni bir widget ekleyin, GameOverScreen:
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 yerine animations paketinden bir OpenContainer widget'ı yerleştirin. viewModel ve açık kapsayıcı geri çağırması 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
),
);
}
}

8. Tebrikler
Tebrikler, Flutter uygulamasına animasyon efektlerini başarıyla eklediniz ve Flutter'ın animasyon sisteminin temel bileşenleri hakkında bilgi edindiniz. Özellikle şu konuları öğrendiniz:
ImplicitlyAnimatedWidgetnasıl kullanılır?ExplicitlyAnimatedWidgetnasıl kullanılır?- Animasyona
CurvesveTweensefektlerini uygulama AnimatedSwitcherveyaPageRouteBuildergibi önceden oluşturulmuş geçiş widget'larını kullanmaanimationspaketindekiFadeThroughTransitionveOpenContainergibi hazır animasyon efektlerini kullanma- Android'de tahmin edilen geri gitme desteği ekleme de dahil olmak üzere varsayılan geçiş animasyonunu özelleştirme

Sırada ne var?
Aşağıdaki codelab'lere göz atın:
- Materyal 3 ile animasyonlu duyarlı uygulama düzeni oluşturma
- Flutter için Material Motion ile güzel geçişler oluşturma
- Flutter uygulamanızı sıkıcı olmaktan çıkarıp güzelleştirme
Alternatif olarak, çeşitli animasyon tekniklerini gösteren animasyon örnek uygulamasını indirebilirsiniz.
Daha fazla bilgi
Daha fazla animasyon kaynağını flutter.dev adresinde bulabilirsiniz:
- Animasyonlara giriş
- Animasyonlar eğitimi (eğitim)
- Implicit animations (eğitim)
- Kapsayıcı özelliklerini canlandırma (çözüm kitabı)
- Widget'ı soldurarak gösterme ve soldurarak gizleme (çözüm kitabı)
- Hero animasyonları
- Sayfa rotası geçişini animasyonla gösterme (cookbook)
- Fizik simülasyonu kullanarak widget'a animasyon ekleme (cookbook)
- Kademeli animasyonlar
- Animasyon ve hareket widget'ları (Widget kataloğu)
Dilerseniz Medium'daki şu makalelere de göz atabilirsiniz:
- Animasyonlarla ilgili ayrıntılı inceleme
- Flutter'da özel örtülü animasyonlar
- Flutter ve Flux / Redux ile animasyon yönetimi
- Size En Uygun Flutter Animasyon Widget'ını Nasıl Seçersiniz?
- Dahili açık animasyonlarla yönlü animasyonlar
- Örtülü animasyonlarla Flutter animasyonlarının temelleri
- AnimatedBuilder veya AnimatedWidget'ı ne zaman kullanmalıyım?