Bu codelab hakkında
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 sağlar.
- Doğal 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ıştırılır ve widget'ın sorunsuz bir şekilde animasyon yapması için aradaki her değer gösterilir. Örtülü animasyonlara örnek olarak
AnimatedSize
,AnimatedScale
veAnimatedPositioned
verilebilir. - Açık animasyonları da önceden oluşturulmuş animasyon efektleridir ancak çalışması için bir
Animation
nesnesi gerekir. Örnekler:SizeTransition
,ScaleTransition
veyaPositionedTransition
. - 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()
verepeat()
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 interpole etmek için kullanılır ve çift,
Offset
veyaColor
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, animasyon başlangıcı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.
Ne oluşturacaksınız?
Bu codelab'de, çeşitli animasyon efektleri ve teknikleri içeren bir çoktan seçmeli test oyunu oluşturacaksınız.
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ş animasyon efektlerini kullanma
- Android'in en son sürümünde bulunan tahmini geri gitme hareketi desteğini ekleyin
Neler öğreneceksiniz?
Bu codelab'de şunları öğreneceksiniz:
- Çok fazla kod yazmadan harika animasyonlar oluşturmak için dolaylı olarak animasyonlu efektleri kullanma.
AnimatedSwitcher
veyaAnimationController
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
'ı kullanma. - Minimum kurulumla şık animasyon efektleri göstermek için
animations
paketini 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 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 alma özelliğini 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'da geliştirme yapmanız gerekir. docs.flutter.dev/desktop adresinde işletim sistemine özgü gereksinimler ayrıntılı olarak açıklanmıştır.
Yükleme işleminizi 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ı indirme
GitHub'daki flutter/samples
deposundan başlangıç uygulamasını kopyalamak için git
'ü 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.
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 varsayılan animasyon efektlerini bulmak için ImplicitlyAnimatedWidget API dokümanlarına göz atın.
Animasyonsuz skor tablosu widget'ını oluşturma
Aşağıdaki kodu içeren 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, 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ı değiştirilir. 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.
Örtük 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(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'ı, 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.
İki değer arasında interpolasyon yapmak için bir ara değer kullanın
AnimatedStar
widget'ının renginin, isActive
alanının true olarak değişmesinden 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ı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ı başka bir 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
arayüzünü karşılayan bir ColorTween
kullanacağız.
Icon
widget'ını seçin ve IDE'nizde "Builder 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); // And modify this line.
},
),
);
}
}
Ardından, yeni animasyonu görmek için uygulamayı sıcak yeniden yükleyin.
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 her 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 gönderilir.
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 dengelenen abartılı bir yay efekti sağlar.
Bu eğrinin AnimatedSize
ve TweenAnimationBuilder
'e uygulandığını görmek için uygulamayı anında yeniden yükleyin.
Yavaş animasyonları etkinleştirmek için Geliştirici Araçları'nı 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 açın.
Widget denetleyicisi açıldığında araç çubuğundaki Animasyonları yavaşlat düğmesini tıklayın.
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.
Belirgin 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 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 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
öğesini SlideTransition
widget'ına sağlanacak bir Animation
öğesine dönüştürmek için bir Tween
öğ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 örnekte, Animation
değerine Curve
değerinin uygulanması ve ardından bu değerin 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ürülmesi için Tween.animate
işlevinin 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);
},
Belirli animasyonlar kullanmanın bir diğer avantajı, bunların birlikte derlenebilmesidir. 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 soru kullanılabilir alanın ortasına yerleştirilir. Animasyon durdurulduğunda ise widget ekranın üst kısmına yapışır. Bu durum, soru kartının nihai konumu animasyon çalışırken konumla eşleşmediği için sarsıntılı bir animasyona neden olur.
Bu sorunu düzeltmek için AnimatedSwitcher
'te, 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ır.
Özet
- Açık animasyonlar, bir
Animation
nesnesi alan animasyon efektleridir (hedefvalue
veduration
alanImplicitlyAnimatedWidgets
'in aksine). Animation
sınıfı, çalışan bir animasyonu temsil eder ancak belirli bir efekti tanımlamaz.- Bir animasyona
Tweens
veCurves
(CurveTween
kullanılarak) uygulamak içinTween().animate
veyaAnimation.drive()
tuşlarını kullanın. - Alt öğelerinin düzenini ayarlamak için
AnimatedSwitcher
öğesininlayoutBuilder
parametresini kullanın.
6. Bir 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 efektlerinin düzgün çalışması için Animation
gerekir. Bu bölümde, AnimationController
kullanarak kendi Animation
nesnelerinizi nasıl oluşturacağınızı ve Tween
'ları bir araya getirmek için TweenSequence
'yi 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:
StatefulWidget
oluşturunAnimationController
sınıfınızdaTicker
sağlamak içinState
sınıfınızdakiSingleTickerProviderStateMixin
mixin'ini kullanınvsync
(TickerProvider
) parametresine geçerliState
nesnesini sağlayarakinitState
yaşam döngüsü yöntemindeAnimationController
'ü başlatın.AnimationController
dinleyicilerini her bilgilendirdiğinde widget'ınızın yeniden oluşturulduğundan emin olmak içinAnimatedBuilder
'u kullanın veyalisten()
vesetState
'ı manuel olarak çağırın.
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 bir AnimationController
oluşturur ve çerçeve didUpdateWidget
'ı çağrdığında widget yapılandırmasının değiştiğini ve yeni bir alt widget olabileceğini bildirmek için 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 amacıyla kullanılır.
Bu widget'ı kullanmak için her yanıt kartını bir CardFlipEffect
widget'ı içine alı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
);
}),
);
}
Ardından, CardFlipEffect
widget'ını kullanarak yanıt kartlarının döndüğünü görmek için uygulamayı anında yeniden yükleyin.
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'ı State
içinde saklaması gerektiğinden StatefulWidget
kullanması gerekiyor. Kendi uygunsuz animasyon efektlerinizi oluşturma hakkında daha fazla bilgi edinmek için AnimatedWidget API dokümanlarını inceleyin.
TweenSequence kullanarak gecikme ekleme
Bu bölümde, her kartın tek tek açılması 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 Animation
oluşturun. Bu işlemin, dart:async
kitaplığındaki Future.delayed
gibi hiçbir yardımcı programı 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
kullanıldığı için 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öreceli 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>([ // 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ı sıcak 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.
7. Özel gezinme geçişlerini kullanma
Şimdiye kadar efektlerin tek bir ekranda nasıl özelleştirileceğini gördük. Ancak animasyonlardan ekranlar arasında geçiş yapmak için de yararlanabilirsiniz. Bu bölümde, pub.dev'deki resmi animations paketi tarafından sağlanan yerleşik animasyon efektlerini ve şık önceden oluşturulmuş animasyon efektlerini kullanarak ekran geçişlerine animasyon efektlerinin nasıl uygulanacağını öğreneceksiniz.
Gezinme geçişini animasyonlu hale getirme
PageRouteBuilder
sınıfı, geçiş animasyonunu özelleştirmenize olanak tanıyan bir Route
sınıfı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
öğ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
(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 önceden oluşturulmuş 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'),
),
Tahmine dayalı geri hareketi animasyonunu özelleştirme
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, bir Flutter uygulamasındaki rotalar arasında gezinirken tahmini geri gitme özelliğini de destekler. PredictiveBackPageTransitionsBuilder
adlı özel bir PageTransitionsBuilder
, sistem tahmini geri gitme 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
öğesini Android'de PredictiveBack
kullanacak ve diğer platformlarda animasyonlar paketindeki geçiş efektini 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ğırma işlevinde yalnızca bir Animation
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( // 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 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 viewModel
öğesini QuestionCard
widget'ına yeni bir geçiş olarak 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
}
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
'yi animations
paketindeki bir OpenContainer
widget'ıyla değiştirin. viewModel
ve açık kapsayıcı 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
),
);
}
}
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?Curves
veTweens
'u bir animasyona uygulamaAnimatedSwitcher
veyaPageRouteBuilder
gibi önceden oluşturulmuş geçiş widget'larını kullanmaanimations
paketindekiFadeThroughTransition
veOpenContainer
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.
Sırada ne var?
Aşağıdaki codelab'lerden bazılarına 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
Dilerseniz çeşitli animasyon tekniklerini gösteren animasyonlar örnek uygulamasını da indirebilirsiniz.
Daha fazla bilgi
flutter.dev'de animasyonlarla ilgili daha fazla kaynak bulabilirsiniz:
- Animasyonlara giriş
- Animasyon eğitimi (eğitim)
- Dolaysız animasyonlar (eğitim)
- Bir kapsayıcının özelliklerini animasyonlu olarak gösterme (rehber)
- Widget'ları yavaşça gösterme ve gizleme (tarif defteri)
- Kahraman animasyonları
- Sayfa yolu geçişine animasyon ekleme (rehber)
- Fiziksel simülasyon kullanarak widget'a animasyon ekleme (rehber)
- Sıralı 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, gizli animasyonlar
- Flutter ve Flux / Redux ile animasyon yönetimi
- Size en uygun Flutter animasyon widget'ını nasıl seçersiniz?
- Yerleşik açık animasyonlar içeren yön animasyonları
- Dolaysız animasyonlarla Flutter animasyonlarının temelleri
- AnimatedBuilder veya AnimatedWidget'ı ne zaman kullanmalıyım?