1. Giriş
Animasyonlar, uygulamanızın kullanıcı deneyimini iyileştirmenin, kullanıcıya önemli bilgileri aktarmanın ve uygulamanızı daha şık ve kullanımı keyifli hale getirmenin mükemmel bir yoludur.
Flutter'ın animasyon çerçevesine genel bakış
Flutter, her karede widget ağacının bir bölümünü yeniden oluşturarak animasyon efektlerini gösterir. Animasyon oluşturmayı ve derlemeyi kolaylaştırmak için önceden oluşturulmuş animasyon efektleri ve diğer API'ler sunar.
- Dolaysız animasyonlar, animasyonların tamamını otomatik olarak çalıştıran önceden oluşturulmuş animasyon efektleridir. Animasyonun hedef değeri değiştiğinde, animasyon mevcut değerden hedef değere kadar çalışır ve widget'ın sorunsuz bir şekilde animasyon yapması için aradaki her değeri gösterir. Örtülü animasyonlara örnek olarak
AnimatedSize
,AnimatedScale
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 ara değerle doldurmak 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, animasyonun başında veya sonunda değişim hızını daha hızlı ya da daha yavaş hale getirmek için yavaşlatma eğrisi uygulamak yaygın bir uygulamadır. Eğriler, 0,0 ile 1,0 arasında bir giriş değeri alır ve 0,0 ile 1,0 arasında bir çıkış değeri döndürür.
Oluşturacağınız uygulama
Bu codelab'de, çeşitli animasyon efektleri ve teknikleri içeren bir çoktan seçmeli test oyunu oluşturacaksınız.
Aşağıdakileri nasıl yapacağınızı öğreneceksiniz:
- Boyutunu ve rengini animasyonlu olarak değiştiren bir widget oluşturma
- 3D kart çevirme efekti oluşturma
- Animasyon paketindeki önceden oluşturulmuş şık animasyon efektlerini kullanın
- Android'in en son sürümünde bulunan tahmini geri gitme hareketi desteğini ekleme
Neler öğreneceksiniz?
Bu codelab'de şunları öğreneceksiniz:
- Çok fazla kod yazmadan harika animasyonlar oluşturmak için dolaylı olarak animasyonlu efektleri kullanma.
AnimatedSwitcher
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
öğesini kullanma. - Az sayıda ayarlamayla şık animasyon efektleri göstermek için
animations
paketini kullanma.
Gerekenler
- Flutter SDK'sı
- VSCode veya Android Studio / IntelliJ gibi bir IDE
2. Flutter geliştirme ortamınızı kurma
Bu laboratuvarı tamamlamak için Flutter SDK ve bir düzenleyici yazılımına ihtiyacınız vardır.
Aşağıdaki cihazlardan herhangi birini kullanarak kod laboratuvarını çalıştırabilirsiniz:
- Bilgisayarınıza bağlı ve Geliştirici moduna ayarlanmış fiziksel bir Android (7. adımda tahmini geri getirmeyi uygulamak için önerilir) veya iOS cihaz.
- iOS simülasyon aracı (Xcode araçlarının yüklenmesi gerekir).
- Android Emülatörü (Android Studio'da kurulum gerektirir).
- Tarayıcı (Hata ayıklama için Chrome gerekir).
- Windows, Linux veya macOS masaüstü bilgisayar. Uygulamayı dağıtmayı planladığınız platformda geliştirme yapmanız gerekir. Bu nedenle, Windows masaüstü uygulaması geliştirmek istiyorsanız uygun derleme zincirine erişmek için Windows'ta geliştirme yapmanız gerekir. docs.flutter.dev/desktop adresinde işletim sistemine özgü gereksinimler ayrıntılı olarak açıklanmıştır.
Yüklemenizi doğrulama
Flutter SDK'nızın doğru şekilde yapılandırıldığından ve yukarıdaki hedef platformlardan en az birinin yüklü olduğundan emin olmak için Flutter Doctor aracını kullanın:
$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.24.2, on macOS 14.6.1 23G93 darwin-arm64, locale en) [✓] Android toolchain - develop for Android devices [✓] Xcode - develop for iOS and macOS [✓] Chrome - develop for the web [✓] Android Studio [✓] IntelliJ IDEA Ultimate Edition [✓] VS Code [✓] Connected device (4 available) [✓] Network resources • No issues found!
3. Başlangıç uygulamasını çalıştırma
Başlatıcı uygulamasını indirin
Başlangıç uygulamasını GitHub'daki flutter/samples deposundan klonlamak için git
simgesini kullanın.
$ git clone https://github.com/flutter/codelabs.git $ cd codelabs/animations/step_01/
Alternatif olarak kaynak kodunu .zip dosyası olarak indirebilirsiniz.
Uygulamayı çalıştırma
Uygulamayı çalıştırmak için flutter run
komutunu kullanın ve android
, ios
veya chrome
gibi bir hedef cihaz belirtin. Desteklenen platformların tam listesi için Desteklenen platformlar sayfasına bakın.
$ flutter run -d android
Ayrıca, tercih ettiğiniz IDE'yi kullanarak uygulamayı çalıştırabilir ve hata ayıklayabilirsiniz. Daha fazla bilgi için resmi Flutter belgelerini inceleyin.
Kodu gezme
Başlatıcı uygulama, model-görüntü-görüntü-model veya MVVM tasarım kalıbını izleyen iki ekrandan oluşan bir çoklu seçimli test oyunudur. QuestionScreen
(Görünüm), kullanıcıya QuestionBank
(Model) sınıfındaki çoktan seçmeli sorular sormak için QuizViewModel
(Görünüm-Model) sınıfını kullanır.
- home_screen.dart: Yeni Oyun düğmesinin yer aldığı bir ekran gösterir.
- main.dart:
MaterialApp
'yi Material 3'ü kullanacak ve ana ekranı gösterecek şekilde yapılandırır - model.dart: Uygulama genelinde kullanılan temel sınıfları tanımlar.
- question_screen.dart: Bilgi yarışması oyununun kullanıcı arayüzünü gösterir.
- view_model.dart:
QuestionScreen
tarafından görüntülenen test oyununun durumunu ve mantığını depolar.
Uygulama henüz animasyonlu efektleri desteklemiyor. Bunun tek istisnası, kullanıcı Yeni Oyun düğmesine bastığında Flutter'ın Navigator
sınıfı tarafından görüntülenen varsayılan görünüm geçişidir.
4. Örtük animasyon efektleri kullanma
Özel yapılandırma gerektirmediklerinden, birçok durumda dolaylı animasyonlar mükemmel bir seçimdir. Bu bölümde, StatusBar
widget'ını animasyonlu bir skor tablosu gösterecek şekilde güncelleyeceksiniz. Sık kullanılan gizli animasyon efektlerini bulmak için ImplicitlyAnimatedWidget API dokümanlarına göz atın.
Animasyonlu olmayan skor tablosu widget'ını oluşturma
Aşağıdaki kodu içeren yeni bir dosya (lib/scoreboard.dart
) oluşturun:
lib/scoreboard.dart
import 'package:flutter/material.dart';
class Scoreboard extends StatelessWidget {
final int score;
final int totalQuestions;
const Scoreboard({
super.key,
required this.score,
required this.totalQuestions,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var i = 0; i < totalQuestions; i++)
Icon(
Icons.star,
size: 50,
color:
score < i + 1 ? Colors.grey.shade400 : Colors.yellow.shade700,
)
],
),
);
}
}
Ardından, Scoreboard
widget'ını StatusBar
widget'ının alt öğelerine ekleyin. Böylece, daha önce puanı ve toplam soru sayısını gösteren Text
widget'larını değiştirmiş olursunuz. Düzenleyiciniz, dosyanın üst kısmına gerekli import "scoreboard.dart"
öğesini otomatik olarak ekler.
lib/question_screen.dart
class StatusBar extends StatelessWidget {
final QuizViewModel viewModel;
const StatusBar({required this.viewModel, super.key});
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Scoreboard( // NEW
score: viewModel.score, // NEW
totalQuestions: viewModel.totalQuestions, // NEW
),
],
),
),
);
}
}
Bu widget'ta her soru için bir yıldız simgesi gösterilir. Bir soru doğru yanıtlandığında, animasyon olmadan anında başka bir yıldız yanar. Aşağıdaki adımlarda, boyutunu ve rengini animasyonlu olarak değiştirerek kullanıcıya puanının değiştiğini bildireceksiniz.
Dolaysız animasyon efekti kullanma
Yıldız etkinleştiğinde scale
tutarını 0.5
yerine 1.0
olarak değiştirmek için AnimatedScale
widget'ı kullanan AnimatedStar
adlı yeni bir widget oluşturun:
lib/scoreboard.dart
import 'package:flutter/material.dart';
class Scoreboard extends StatelessWidget {
final int score;
final int totalQuestions;
const Scoreboard({
super.key,
required this.score,
required this.totalQuestions,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var i = 0; i < totalQuestions; i++)
AnimatedStar( // NEW
isActive: score > i, // NEW
) // NEW
],
),
);
}
}
class AnimatedStar extends StatelessWidget { // Add from here...
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
duration: _duration,
child: Icon(
Icons.star,
size: 50,
color: isActive ? _activatedColor : _deactivatedColor,
),
);
}
} // To here.
Artık kullanıcı bir soruyu doğru yanıtladığında AnimatedStar
widget'ı, boyutunu gizli bir animasyon kullanarak güncelliyor. Icon
'nin color
'si burada animasyonlu değildir. Yalnızca AnimatedScale
widget'ı tarafından yapılan scale
animasyonlu olur.
İki değer arasında interpolasyon yapmak için tween kullanın
isActive
alanının true olarak değişmesinden hemen sonra AnimatedStar
widget'ının renginin değiştiğini fark edin.
Animasyonlu bir renk efekti elde etmek için AnimatedContainer
widget'ını (ImplicitlyAnimatedWidget
sınıfının başka bir alt sınıfıdır) kullanmayı deneyebilirsiniz. Bu widget, renk de dahil olmak üzere tüm özelliklerini otomatik olarak animasyonlu hale getirebilir. Maalesef widget'ımızın kapsayıcı değil, simge göstermesi gerekiyor.
Simgelerin şekilleri arasında geçiş efektleri uygulayan AnimatedIcon
seçeneğini de deneyebilirsiniz. Ancak AnimatedIcons
sınıfında yıldız simgesi için varsayılan bir uygulama yoktur.
Bunun yerine, parametre olarak Tween
alan TweenAnimationBuilder
adlı ImplicitlyAnimatedWidget
alt sınıfını kullanacağız. Ara değer, iki değer (begin
ve end
) alan ve animasyon tarafından görüntülenebilmesi için aralarındaki değerleri hesaplayan bir sınıftır. Bu örnekte, animasyon efektimizi oluşturmak için gereken Tween<Color>
arayüzünü karşılayan bir ColorTween
kullanacağız.
Icon
widget'ını seçin ve IDE'nizde "Geliştirici ile sarma" hızlı işlemini kullanarak adı TweenAnimationBuilder
olarak değiştirin. Ardından süreyi ve ColorTween
değerini girin.
lib/scoreboard.dart
class AnimatedStar extends StatelessWidget {
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
duration: _duration,
child: TweenAnimationBuilder( // Add from here...
duration: _duration,
tween: ColorTween(
begin: _deactivatedColor,
end: isActive ? _activatedColor : _deactivatedColor,
),
builder: (context, value, child) { // To here.
return Icon(
Icons.star,
size: 50,
color: value, // Modify from here...
);
}, // To here.
),
);
}
}
Ardından, yeni animasyonu görmek için uygulamayı sıcak yeniden yükleyin.
ColorTween
öğemizin end
değerinin, isActive
parametresinin değerine göre değiştiğini unutmayın. Bunun nedeni, TweenAnimationBuilder
öğesinin Tween.end
değeri değiştiğinde animasyonunu yeniden çalıştırmasıdır. Bu durumda yeni animasyon, mevcut animasyon değerinden yeni bitiş değerine kadar çalışır. Bu sayede rengi istediğiniz zaman (animasyon çalışırken bile) değiştirebilir ve doğru ara değerlerle sorunsuz bir animasyon efekti görüntüleyebilirsiniz.
Eğri uygulama
Bu animasyon efektlerinin ikisi de sabit bir hızda çalışır ancak animasyonlar genellikle hızlandığında veya yavaşlatıldığında görsel açıdan daha ilgi çekici ve bilgilendirici olur.
Curve
, bir parametrenin zaman içindeki değişim hızını tanımlayan bir geçiş işlevi uygular. Flutter, Curves
sınıfında easeIn
veya easeOut
gibi önceden oluşturulmuş bir yumuşatma eğrileri koleksiyonuyla birlikte sunulur.
Bu diyagramlar (Curves
API doküman sayfasında mevcuttur), eğrilerin işleyiş şekli hakkında ipucu verir. Eğriler, 0,0 ile 1,0 arasında bir giriş değerini (x ekseninde gösterilir) 0,0 ile 1,0 arasında bir çıkış değerine (y ekseninde gösterilir) dönüştürür. Bu şemalarda, yumuşatma eğrisi kullanıldığında çeşitli animasyon efektlerinin nasıl göründüğüne dair bir önizleme de gösterilir.
AnimatedStar widget'ında _curve
adlı yeni bir alan oluşturun ve bu alanı AnimatedScale
ve TweenAnimationBuilder
widget'larına parametre olarak iletin.
lib/scoreboard.dart
class AnimatedStar extends StatelessWidget {
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
final Curve _curve = Curves.elasticOut; // NEW
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
curve: _curve, // NEW
duration: _duration,
child: TweenAnimationBuilder(
curve: _curve, // NEW
duration: _duration,
tween: ColorTween(
begin: _deactivatedColor,
end: isActive ? _activatedColor : _deactivatedColor,
),
builder: (context, value, child) {
return Icon(
Icons.star,
size: 50,
color: value,
);
},
),
);
}
}
Bu örnekte elasticOut
eğrisi, bir yay hareketiyle başlayan ve sona doğru dengeye gelen abartılı bir yay efekti sağlar.
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 DevTools'u kullanma
Flutter DevTools, animasyon efektlerinde hata ayıklama için uygulamanızdaki tüm animasyonları yavaşlatarak animasyonları daha net bir şekilde görmenizi sağlar.
DevTools'u açmak için uygulamanın hata ayıklama modunda çalıştığından emin olun ve VSCode'taki Hata ayıklama araç çubuğundan veya IntelliJ / Android Studio'daki Hata ayıklama aracı penceresinden Widget Denetleyici'yi seçerek Flutter DevTools'u aç düğmesini tıklayın.
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.
Açık bir animasyon efekti kullanma
Belirli bir animasyon efektiyle başlamak için Card
widget'ını AnimatedSwitcher
ile sarın.
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.question,
super.key,
});
@override
Widget build(BuildContext context) {
return AnimatedSwitcher( // NEW
duration: const Duration(milliseconds: 300), // NEW
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
), // NEW
);
}
}
AnimatedSwitcher
varsayılan olarak bir geçiş efekti kullanır ancak transitionBuilder
parametresini kullanarak bu ayarı geçersiz kılabilirsiniz. Geçiş oluşturucu, AnimatedSwitcher
öğesine iletilen alt widget'ı ve bir Animation
nesnesi sağlar. Bu, açık bir animasyon kullanmak için mükemmel bir fırsattır.
Bu codelab'de kullanacağımız ilk açık animasyon SlideTransition
'dur. Bu animasyon, gelen ve giden widget'ların arasında hareket edeceği başlangıç ve bitiş ofsetini tanımlayan bir Animation<Offset>
alır.
Ara geçişler, Animation
değerini ara geçiş uygulanmış başka bir Animation
değerine dönüştüren animate()
yardımcı işlevine sahiptir. Bu, AnimatedSwitcher
tarafından sağlanan Animation<double>
öğesini SlideTransition
widget'ına sağlanacak bir Animation<Offset>
öğesine dönüştürmek için bir Tween<Offset>
öğesinin kullanılabileceği anlamına gelir.
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.question,
super.key,
});
@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
transitionBuilder: (child, animation) { // Add from here...
final curveAnimation =
CurveTween(curve: Curves.easeInCubic).animate(animation);
final offsetAnimation =
Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero)
.animate(curveAnimation);
return SlideTransition(position: offsetAnimation, child: child);
}, // To here.
duration: const Duration(milliseconds: 300),
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
),
);
}
}
Bu işlemde, Animation
'ye Curve
uygulamak ve ardından 0,0 ile 1,0 arasında değişen bir Tween<double>
'den x ekseninde -0,1 ile 0,0 arasında geçiş yapan bir Tween<Offset>
'ye dönüştürmek için Tween.animate
'nin kullanıldığını unutmayın.
Alternatif olarak, Animasyon sınıfında herhangi bir Tween
(veya Animatable
) değerini alıp yeni bir Animation
değerine dönüştüren bir drive()
işlevi vardır. Bu sayede tween'ler "zincirlenebilir" ve ortaya çıkan kod daha kısa olur:
lib/question_screen.dart
transitionBuilder: (child, animation) {
var offsetAnimation = animation
.drive(CurveTween(curve: Curves.easeInCubic))
.drive(Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero));
return SlideTransition(position: offsetAnimation, child: child);
},
Belirgin animasyonları kullanmanın bir diğer avantajı da kolayca bir araya getirilebilmeleridir. SlideTransition widget'ını sarmalayarak aynı kavisli animasyonu kullanan başka bir açık animasyon (FadeTransition) ekleyin.
lib/question_screen.dart
return AnimatedSwitcher(
transitionBuilder: (child, animation) {
final curveAnimation =
CurveTween(curve: Curves.easeInCubic).animate(animation);
final offsetAnimation =
Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero)
.animate(curveAnimation);
final fadeInAnimation = curveAnimation; // NEW
return FadeTransition( // NEW
opacity: fadeInAnimation, // NEW
child: SlideTransition(position: offsetAnimation, child: child), // NEW
); // NEW
},
layoutBuilder'ı özelleştirme
AnimationSwitcher ile ilgili küçük bir sorun fark edebilirsiniz. QuestionCard yeni bir soruya geçtiğinde, animasyon çalışırken soruyu kullanılabilir alanın ortasına yerleştirir. Animasyon durdurulduğunda ise widget ekranın üst kısmına yapışır. Bu durum, soru kartının nihai konumu animasyon çalışırkenki konumla eşleşmediği için sarsıntılı bir animasyona neden olur.
Bu sorunu düzeltmek için AnimatedSwitcher'da, düzeni tanımlamak için kullanılabilecek bir layoutBuilder parametresi de vardır. Kartı ekranın üst kısmına hizalayacak şekilde düzen oluşturucuyu yapılandırmak için bu işlevi kullanın:
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
layoutBuilder: (currentChild, previousChildren) {
return Stack(
alignment: Alignment.topCenter,
children: <Widget>[
...previousChildren,
if (currentChild != null) currentChild,
],
);
},
Bu kod, AnimatedSwitcher sınıfındaki defaultLayoutBuilder işlevinin değiştirilmiş bir sürümüdür ancak Alignment.center
yerine Alignment.topCenter
kullanılır.
Özet
- Belirli animasyonlar, bir Animation nesnesi alan animasyon efektleridir (hedef değer ve süre alan ImplicitlyAnimatedWidgets'ın aksine).
- Animation sınıfı, çalışan bir animasyonu temsil eder ancak belirli bir efekti tanımlamaz.
- Bir animasyona Tween ve Eğri (CurveTween kullanarak) uygulamak için Tween().animate veya Animation.drive() işlevini kullanın.
- AnimatedSwitcher'ın layoutBuilder parametresini kullanarak alt öğelerinin düzenini ayarlayın.
6. Animasyonun durumunu kontrol etme
Şimdiye kadar her animasyon, çerçeve tarafından otomatik olarak çalıştırıldı. Örtük animasyonlar otomatik olarak çalışır ve açık animasyon efektleri için animasyonların düzgün çalışması gerekir. Bu bölümde, AnimationController kullanarak kendi animasyon nesnelerinizi nasıl oluşturacağınızı ve Tween'leri birleştirmek için TweenSequence'i nasıl kullanacağınızı öğreneceksiniz.
AnimationController kullanarak animasyon çalıştırma
AnimationController kullanarak animasyon oluşturmak için aşağıdaki adımları uygulamanız gerekir:
- StatefulWidget oluşturma
- AnimationController'a Ticker sağlamak için State sınıfınızda SingleTickerProviderStateMixin mixin'ini kullanın
- initState yaşam döngüsü yönteminde AnimationController'ı başlatın.
vsync
(TickerProvider) parametresine mevcut State nesnesini sağlayın. - AnimationController dinleyicilerini her bilgilendirdiğinde widget'ınızın yeniden oluşturulduğundan emin olmak için AnimatedBuilder'ı kullanın veya listen() ve setState'i manuel olarak çağırın.
flip_effect.dart adlı yeni bir dosya oluşturun ve aşağıdaki kodu kopyalayıp yapıştırın:
lib/flip_effect.dart
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
class CardFlipEffect extends StatefulWidget {
final Widget child;
final Duration duration;
const CardFlipEffect({
super.key,
required this.child,
required this.duration,
});
@override
State<CardFlipEffect> createState() => _CardFlipEffectState();
}
class _CardFlipEffectState extends State<CardFlipEffect>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
Widget? _previousChild;
@override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: widget.duration);
_animationController.addListener(() {
if (_animationController.value == 1) {
_animationController.reset();
}
});
}
@override
void didUpdateWidget(covariant CardFlipEffect oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.child.key != oldWidget.child.key) {
_handleChildChanged(widget.child, oldWidget.child);
}
}
void _handleChildChanged(Widget newChild, Widget previousChild) {
_previousChild = previousChild;
_animationController.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateX(_animationController.value * math.pi),
child: _animationController.isAnimating
? _animationController.value < 0.5
? _previousChild
: Transform.flip(flipY: true, child: child)
: child,
);
},
child: widget.child,
);
}
}
Bu sınıf bir AnimationController oluşturur ve çerçeve, widget yapılandırmasının değiştiğini ve yeni bir alt widget olabileceğini bildirmek için didUpdateWidget'ı çağrdığında animasyonu yeniden çalıştırır.
AnimatedBuilder, AnimationController dinleyicilerini her bilgilendirdiğinde widget ağacının yeniden oluşturulmasını sağlar. Transform widget'ı ise kartın çevrilmesini simüle etmek için 3D dönme efekti uygulamak üzere kullanılır.
Bu widget'ı kullanmak için her bir yanıt kartını bir CardFlipEffect widget'ı içine alın. Kart widget'ına bir key
sağladığınızdan emin olun:
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
childAspectRatio: 5 / 2,
children: List.generate(answers.length, (index) {
var color = Theme.of(context).colorScheme.primaryContainer;
if (correctAnswer == index) {
color = Theme.of(context).colorScheme.tertiaryContainer;
}
return CardFlipEffect( // NEW
duration: const Duration(milliseconds: 300), // NEW
child: Card.filled( // NEW
key: ValueKey(answers[index]), // NEW
color: color,
elevation: 2,
margin: EdgeInsets.all(8),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => onTapped(index),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: Text(
answers.length > index ? answers[index] : '',
style: Theme.of(context).textTheme.titleMedium,
overflow: TextOverflow.clip,
),
),
),
),
), // NEW
);
}),
);
}
Artık CardFlipEffect widget'ını kullanarak yanıt kartlarının çevrilmesini görmek için uygulamayı sıcak yeniden yükleyin.
Bu sınıfın, belirgin bir animasyon efekti gibi göründüğünü fark edebilirsiniz. Aslında, kendi sürümünüzü uygulamak için AnimatedWidget sınıfını doğrudan genişletmek genellikle iyi bir fikirdir. Maalesef bu sınıfın önceki widget'ı durumunda saklaması gerektiğinden StatefulWidget kullanması gerekiyor. Kendi uygunsuz animasyon efektlerinizi oluşturma hakkında daha fazla bilgi edinmek için AnimatedWidget API dokümanlarına bakın.
TweenSequence kullanarak gecikme ekleme
Bu bölümde, her kartın tek tek dönmesi için CardFlipEffect widget'ına bir gecikme eklersiniz. Başlamak için delayAmount
adlı yeni bir alan ekleyin.
lib/flip_effect.dart
class CardFlipEffect extends StatefulWidget {
final Widget child;
final Duration duration;
final double delayAmount; // NEW
const CardFlipEffect({
super.key,
required this.child,
required this.duration,
required this.delayAmount, // NEW
});
@override
State<CardFlipEffect> createState() => _CardFlipEffectState();
}
Ardından delayAmount
öğesini AnswerCards
derleme yöntemine ekleyin.
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
childAspectRatio: 5 / 2,
children: List.generate(answers.length, (index) {
var color = Theme.of(context).colorScheme.primaryContainer;
if (correctAnswer == index) {
color = Theme.of(context).colorScheme.tertiaryContainer;
}
return CardFlipEffect(
delayAmount: index.toDouble() / 2, // NEW
duration: const Duration(milliseconds: 300),
child: Card.filled(
key: ValueKey(answers[index]),
Ardından _CardFlipEffectState
içinde, TweenSequence
kullanarak gecikmeyi uygulayan yeni bir animasyon oluşturun. Bu işlemin, dart:async
kitaplığındaki Future.delayed
gibi hiçbir yardımcı programı kullanıp kullanmadığını unutmayın. Bunun nedeni, gecikmenin animasyonun bir parçası olması ve widget'ın AnimationController'ı kullanırken açıkça kontrol etmediği bir şey olmasıdır. Aynı TickerProvider'ı kullandığından, DevTools'ta yavaş animasyonlar etkinleştirildiğinde animasyon efektinin hata ayıklanmasını kolaylaştırır.
TweenSequence
kullanmak için iki TweenSequenceItem
oluşturun. Bunlardan biri, animasyonu göreli bir süre boyunca 0 değerinde tutan bir ConstantTween
, diğeri ise 0.0
ile 1.0
arasında değişen normal bir Tween
içermelidir.
lib/flip_effect.dart
class _CardFlipEffectState extends State<CardFlipEffect>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
Widget? _previousChild;
late final Animation<double> _animationWithDelay; // NEW
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: widget.duration * (widget.delayAmount + 1));
_animationController.addListener(() {
if (_animationController.value == 1) {
_animationController.reset();
}
});
_animationWithDelay = TweenSequence<double>([ // NEW
if (widget.delayAmount > 0) // NEW
TweenSequenceItem( // NEW
tween: ConstantTween<double>(0.0), // NEW
weight: widget.delayAmount, // NEW
), // NEW
TweenSequenceItem( // NEW
tween: Tween(begin: 0.0, end: 1.0), // NEW
weight: 1.0, // NEW
), // NEW
]).animate(_animationController); // NEW
}
Son olarak, build yönteminde AnimationController'ın animasyonunu yeni gecikmeli animasyonla değiştirin.
lib/flip_effect.dart
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationWithDelay, // Modify this line
builder: (context, child) {
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateX(_animationWithDelay.value * math.pi), // And this line
child: _animationController.isAnimating
? _animationWithDelay.value < 0.5 // And this one.
? _previousChild
: Transform.flip(flipY: true, child: child)
: child,
);
},
child: widget.child,
);
}
Şimdi uygulamayı anında yeniden yükleyin ve kartların tek tek açılmasını izleyin. Transform
widget'ı tarafından sağlanan 3D efektin perspektifini değiştirmeyi deneyebilirsiniz.
7. Özel gezinme geçişlerini kullanma
Şimdiye kadar efektlerin tek bir ekranda nasıl özelleştirileceğini gördük. Ancak animasyonları ekranlar arasında geçiş yapmak için de kullanabilirsiniz. Bu bölümde, yerleşik animasyon efektlerini ve pub.dev'deki resmi animations paketi tarafından sağlanan önceden oluşturulmuş animasyon efektlerini kullanarak ekran geçişlerine animasyon efektleri nasıl uygulayacağınızı öğreneceksiniz.
Gezinme geçişine animasyon ekleme
PageRouteBuilder
sınıfı, geçiş animasyonunu özelleştirmenize olanak tanıyan bir Route
sınıfıdır. Bu, Navigator tarafından çalıştırılan gelen ve giden animasyonu temsil eden iki Animation nesnesi sağlayan transitionBuilder
geri çağırma işlevini geçersiz kılmanıza olanak tanır.
Geçiş animasyonunu özelleştirmek için MaterialPageRoute
öğesini PageRouteBuilder
ile değiştirin. Kullanıcı HomeScreen
öğesinden QuestionScreen
öğesine gittiğinde geçiş animasyonunu özelleştirmek için de MaterialPageRoute
öğesini PageRouteBuilder
ile değiştirin. Yeni ekranın önceki ekranın üzerine yavaşça gelmesini sağlamak için FadeTransition (açıkça animasyonlu bir widget) kullanın.
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
PageRouteBuilder( // NEW
pageBuilder: (context, animation, secondaryAnimation) { // NEW
return QuestionScreen(); // NEW
}, // NEW
transitionsBuilder: // NEW
(context, animation, secondaryAnimation, child) { // NEW
return FadeTransition( // NEW
opacity: animation, // NEW
child: child, // NEW
); // NEW
}, // NEW
), // NEW
);
},
child: Text('New Game'),
),
Animasyon paketi, FadeThroughTransition gibi önceden oluşturulmuş animasyon efektleri sunar. Animasyon paketini içe aktarın ve FadeTransition öğesini FadeThroughTransition widget'ı ile değiştirin:
lib/home_screen.dart
import 'package;animations/animations.dart';
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return const QuestionScreen();
},
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeThroughTransition( // NEW
animation: animation, // NEW
secondaryAnimation: secondaryAnimation, // NEW
child: child, // NEW
); // NEW
},
),
);
},
child: Text('New Game'),
),
Tahmine dayalı geri hareketi animasyonunu özelleştirme
Tahmini geri, kullanıcının gezinmeden önce mevcut rotanın veya uygulamanın arkasında ne olduğunu görmek için bakmasına olanak tanıyan yeni bir Android özelliğidir. Bir bakışta animasyonu, kullanıcı ekranı geri sürüklerken parmağının konumuna göre belirlenir.
Flutter, gezinme yığınında açılacak rotaları olmadığında veya başka bir deyişle, geri tuşuna basıldığında uygulamadan çıkıldığında bu özelliği sistem düzeyinde etkinleştirerek sistem tahmini geri özelliğini destekler. Bu animasyon Flutter tarafından değil, sistem tarafından yönetilir.
Flutter, Flutter uygulamasındaki rotalar arasında gezinirken tahmini geri özelliğini de destekler. PredictiveBackPageTransitionsBuilder
adlı özel bir PageTransitionsBuilder, sistem tahmini geri hareketlerini dinler ve sayfa geçişini hareketin ilerleme hızına göre yönlendirir.
Tahmini geri düğmesi yalnızca Android U ve sonraki sürümlerde desteklenir ancak Flutter, orijinal geri hareketi davranışına ve ZoomPageTransitionBuilder'a sorunsuz bir şekilde geri döner. Kendi uygulamanızda nasıl ayarlayacağınızla ilgili bir bölüm de dahil olmak üzere daha fazla bilgi için blog yayınımızı inceleyin.
Uygulamanızın ThemeData yapılandırmasında PageTransitionsTheme'i, Android'de PredictiveBack'i ve diğer platformlarda animations paketindeki geçiş efektinin solma özelliğini kullanacak şekilde yapılandırın:
lib/main.dart
import 'package:animations/animations.dart'; // NEW
import 'package:flutter/material.dart';
import 'home_screen.dart';
void main() {
runApp(MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), // NEW
TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.macOS: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.windows: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.linux: FadeThroughPageTransitionsBuilder(), // NEW
},
),
),
home: HomeScreen(),
);
}
}
Artık Navigator.push() geri çağırma işlevini MaterialPageRoute olarak değiştirebilirsiniz.
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
MaterialPageRoute(builder: (context) { // NEW
return const QuestionScreen(); // NEW
}), // NEW
);
},
child: Text('New Game'),
),
Mevcut soruyu değiştirmek için FadeThroughTransition'ı kullanma
AnimatedSwitcher widget'ı, oluşturucu geri çağırma işlevinde yalnızca bir animasyon sağlar. Bu sorunu gidermek için animations
paketi bir PageTransitionSwitcher sağlar.
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.question,
super.key,
});
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher( // NEW
layoutBuilder: (entries) { // NEW
return Stack( // NEW
alignment: Alignment.topCenter, // NEW
children: entries, // NEW
); // NEW
}, // NEW
transitionBuilder: (child, animation, secondaryAnimation) { // NEW
return FadeThroughTransition( // NEW
animation: animation, // NEW
secondaryAnimation: secondaryAnimation, // NEW
child: child, // NEW
); // NEW
}, // NEW
duration: const Duration(milliseconds: 300),
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
),
);
}
}
OpenContainer'ı kullanma
animations
paketindeki OpenContainer widget'ı, iki widget arasında görsel bir bağlantı oluşturmak için genişleyen bir kapsayıcı dönüştürme animasyon efekti sağlar.
Başlangıçta closedBuilder
tarafından döndürülen widget gösterilir ve kapsayıcıya dokunulduğunda veya openContainer
geri çağırma işlevi çağrıldığında openBuilder
tarafından döndürülen widget'a genişler.
openContainer
geri çağırma işlevini görünüm modeline bağlamak için QuestionCard widget'ına yeni bir viewModel geçişi ekleyin ve "Oyun Bitti" ekranını göstermek için kullanılacak bir geri çağırma işlevi depolayın:
lib/question_screen.dart
class QuestionScreen extends StatefulWidget {
const QuestionScreen({super.key});
@override
State<QuestionScreen> createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> {
late final QuizViewModel viewModel =
QuizViewModel(onGameOver: _handleGameOver);
VoidCallback? _showGameOverScreen; // NEW
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: viewModel,
builder: (context, child) {
return Scaffold(
appBar: AppBar(
actions: [
TextButton(
onPressed:
viewModel.hasNextQuestion && viewModel.didAnswerQuestion
? () {
viewModel.getNextQuestion();
}
: null,
child: const Text('Next'),
)
],
),
body: Center(
child: Column(
children: [
QuestionCard( // NEW
onChangeOpenContainer: _handleChangeOpenContainer, // NEW
question: viewModel.currentQuestion?.question, // NEW
viewModel: viewModel, // NEW
), // NEW
Spacer(),
AnswerCards(
onTapped: (index) {
viewModel.checkAnswer(index);
},
answers: viewModel.currentQuestion?.possibleAnswers ?? [],
correctAnswer: viewModel.didAnswerQuestion
? viewModel.currentQuestion?.correctAnswer
: null,
),
StatusBar(viewModel: viewModel),
],
),
),
);
},
);
}
void _handleChangeOpenContainer(VoidCallback openContainer) { // NEW
_showGameOverScreen = openContainer; // NEW
} // NEW
void _handleGameOver() { // NEW
if (_showGameOverScreen != null) { // NEW
_showGameOverScreen!(); // NEW
} // NEW
} // NEW
}
GameOverScreen adlı yeni bir widget ekleyin:
lib/question_screen.dart
class GameOverScreen extends StatelessWidget {
final QuizViewModel viewModel;
const GameOverScreen({required this.viewModel, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Scoreboard(
score: viewModel.score,
totalQuestions: viewModel.totalQuestions,
),
Text(
'You Win!',
style: Theme.of(context).textTheme.displayLarge,
),
Text(
'Score: ${viewModel.score} / ${viewModel.totalQuestions}',
style: Theme.of(context).textTheme.displaySmall,
),
ElevatedButton(
child: Text('OK'),
onPressed: () {
Navigator.popUntil(context, (route) => route.isFirst);
},
),
],
),
),
);
}
}
QuestionCard widget'ında, Card öğesini animations paketindeki OpenContainer widget'ıyla değiştirin. viewModel ve open container geri çağırma işlevi için iki yeni alan ekleyin:
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.onChangeOpenContainer,
required this.question,
required this.viewModel,
super.key,
});
final ValueChanged<VoidCallback> onChangeOpenContainer;
final QuizViewModel viewModel;
static const _backgroundColor = Color(0xfff2f3fa);
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
child: OpenContainer( // NEW
key: ValueKey(question), // NEW
tappable: false, // NEW
closedColor: _backgroundColor, // NEW
closedShape: const RoundedRectangleBorder( // NEW
borderRadius: BorderRadius.all(Radius.circular(12.0)), // NEW
), // NEW
closedElevation: 4, // NEW
closedBuilder: (context, openContainer) { // NEW
onChangeOpenContainer(openContainer); // NEW
return ColoredBox( // NEW
color: _backgroundColor, // NEW
child: Padding( // NEW
padding: const EdgeInsets.all(16.0), // NEW
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
);
},
openBuilder: (context, closeContainer) { // NEW
return GameOverScreen(viewModel: viewModel); // NEW
}, // NEW
),
);
}
}
8. Tebrikler
Tebrikler, bir Flutter uygulamasına animasyon efektleri eklemeyi başardınız ve Flutter'ın animasyon sisteminin temel bileşenleri hakkında bilgi edindiniz. Özellikle şunları öğrendiniz:
- ImplicitlyAnimatedWidget nasıl kullanılır?
- ExplicitlyAnimatedWidget nasıl kullanılır?
- Eğriler ve ara animasyonlar animasyonlara nasıl uygulanır?
- AnimatedSwitcher veya PageRouteBuilder gibi önceden oluşturulmuş geçiş widget'larını kullanma
animations
paketindeki FadeThroughTransition ve OpenContainer gibi önceden oluşturulmuş animasyon efektlerini kullanma- Android'de Tahmini Geri özelliği için destek ekleme de dahil olmak üzere varsayılan geçiş animasyonunu özelleştirme.
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
Alternatif olarak, çeşitli animasyon tekniklerini gösteren animasyonlar örnek uygulamasını indirebilirsiniz.
Daha fazla bilgi
flutter.dev'de animasyonlarla ilgili daha fazla kaynak bulabilirsiniz:
- 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?