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ğerinden oluşur.
Listenablealt 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()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 animasyonlar, başlangıç ve bitiş değerleri arasındaki değerleri enterpolasyon yapmak 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 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.

Şunları nasıl yapacağınızı öğreneceksiniz:
- 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 kullanılabilen 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
animationspaketinin nasıl kullanılacağı.
İ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 klonlamak 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-görünüm-görünüm-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:
QuestionScreentarafından görüntülenen, test oyununun durumunu ve mantığını saklar.

Uygulama, kullanıcının Yeni Oyun düğmesine basması durumunda Flutter'ın Navigator sınıfı tarafından gösterilen 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 özel bir 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ğini fark 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'ı 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); // And modify this line.
},
),
);
}
}
Yeni animasyonu görmek için uygulamayı anında yeniden yükleyin.

ColorTween öğemizin end değerinin, isActive parametresinin değerine göre değiştiğini unutmayın. Bunun nedeni, Tween.end değerinin her değiştiğinde TweenAnimationBuilder animasyonunu yeniden çalıştırmasıdır. Bu durumda, yeni animasyon mevcut animasyon değerinden yeni bitiş değerine kadar çalışır. Bu sayede, 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şatma 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.
Geliştirici araçlarını açmak için uygulamanın hata ayıklama modunda çalıştığından emin olun ve VSCode'daki Debug toolbar'da (Hata ayıklama araç çubuğu) Widget Inspector'ı (Widget inceleyici) seçerek veya IntelliJ / Android Studio'daki Debug tool window'da (Hata ayıklama aracı penceresi) Open Flutter DevTools (Flutter geliştirici araçlarını aç) düğmesini seçerek Widget Inspector'ı açın.


Widget inceleyici açıldıktan sonra araç çubuğundaki 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 bunu 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 SlideTransition, gelen ve giden widget'ların arasında hareket edeceği başlangıç ve bitiş uzaklığını tanımlayan bir Animation<Offset> alır.
Ara animasyonlarda, herhangi bir Animation öğesini ara animasyon uygulanmış başka bir Animation öğesine dönüştüren animate() yardımcı işlevi bulunur. Bu, AnimatedSwitcher tarafından sağlanan Animation değerinin Tween kullanılarak SlideTransition widget'ına sağlanacak bir Animation değerine 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şlevin, Tween.animate kullanarak Animation öğesine Curve uyguladığını ve ardından bunu 0,0 ile 1,0 arasında değişen bir Tween değerinden 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() değerini alan ve bunu yeni bir Animation değerine dönüştüren bir Tween (veya Animatable) işlevi bulunur. 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. SlideTransition widget'ını sarmalayarak aynı eğri 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 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 konumla 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) animasyona 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 mevcutStatenesnesinivsync(TickerProvider) parametresine sağlayın.AnimationController, dinleyicilerini her bilgilendirdiğinde widget'ınızın yeniden oluşturulduğundan emin olun. BunuAnimatedBuilderkullanarak veyalisten()vesetState'yi manuel olarak çağırarak yapabilirsiniz.
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 AnimationController kullanılırken widget'ın açıkça kontrol ettiği bir şey olmaması ve animasyonun bir parçası olması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'nın 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'ının sağladığı 3D efektin perspektifini değiştirmeyi deneyin.

7. Özel gezinme geçişlerini kullanma
Şimdiye kadar efektleri tek bir ekranda nasıl özelleştireceğ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ğırma işlevini 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 yerine 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 öğesini 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 rotanın 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 açılacak 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'),
),
Mevcut 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 çözmek 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şler.
openContainer geri çağırmasını görünüm modeline bağlamak için viewModel öğesini QuestionCard widget'ına ileten yeni bir geçiş ekleyin ve "Oyun Bitti" ekranını göstermek için kullanılacak bir geri çağırmayı saklayı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
}
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 efektleri eklemeyi ve Flutter'ın animasyon sisteminin temel bileşenlerini öğrendiniz. Ö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:
- Material 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)
- Bir kapsayıcının özelliklerini animasyonla hareketlendirme (çözüm kitabı)
- Widget'ı soldurarak gösterme ve gizleme (yemek 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:
- Animasyonla 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'da animasyonun temelleri
- AnimatedBuilder veya AnimatedWidget'ı ne zaman kullanmalıyım?