Aşamalı bir animasyon sıralı veya örtüşen animasyonlardan oluşur. Animasyon, bir değişikliğin diğerinden sonra meydana geldiği tamamen sıralı olabilir veya kısmen veya tamamen örtüşebilir. Animasyon sıralı olduğunda öğeler, her öğenin başlangıç zamanları arasında hafif bir gecikmeyle sırayla canlandırılır. Bu, animasyonun öğeler arasında bir kerede değil, aşamalar halinde hareket ediyor gibi göründüğü basamaklı veya dalgalanma efekti yaratır. Kademeli animasyonlar, bir tür mikro etkileşim olarak kabul edilir çünkü kullanıcıya bir arayüz aracılığıyla rehberlik eden incelikli, etkileşimli geri bildirim sağlayarak kullanıcı deneyimini geliştirir. Flutter'da, örtülü veya açık animasyonu kullanarak incelikli animasyonlar hazırlayarak mikro etkileşimler oluşturabilirsiniz. Bağlam açısından, örtülü animasyonlar basit ve kullanımı kolay olacak şekilde tasarlanmıştır çünkü animasyon ayrıntıları soyutlanmıştır, açık animasyonlar ise karmaşık animasyonlar için daha uygundur çünkü animasyon süreci üzerinde tam kontrol sağlarlar. Bu, özellikle animasyon üzerinde daha ayrıntılı kontrole sahip olmanız gerektiğinde kullanılır. Bu makalede, mikro etkileşimler kavramını derinlemesine inceleyeceğiz, ardından bir mikro etkileşim kullanım örneği için, bir sütun widget'ında bulunan çocukları canlandıran kademeli bir animasyon oluşturmak için açık animasyon kullanacağız. Mikro Etkileşim Kavramı Nedir? Mükemmel ürünler, hem özellikleri hem de ayrıntıları iyi bir şekilde sunan ürünlerdir. Özellikler insanları ürünlerinize getirir, ancak ayrıntılar onları korur. Bu ayrıntılar uygulamanızı diğerlerinden farklı kılan şeylerdir. Mikro etkileşimler ile kullanıcılarınıza keyifli geri bildirimler sağlayarak bu detayları oluşturabilirsiniz. Mikro etkileşim kavramı, küçük ayrıntıların genel kullanıcı deneyimi üzerinde büyük bir etkiye sahip olabileceği fikrine dayanmaktadır. Mikro etkileşimler, geri bildirimin veya bir eylemin sonucunun iletilmesi gibi temel işlevlere hizmet etmek için kullanılabilir. Mikro etkileşim örnekleri şunları içerir: Düğme animasyonları: Düğmenin üzerine gelindiğinde veya basıldığında renk veya boyut değişir. Yükleme göstergeleri: Kullanıcıya bir işlemin devam ettiğini gösteren animasyonlar. Kaydırma hareketleri: Kaydırma hareketlerine yanıt veren animasyonlar. Gezinme geçişleri: Ekranlar arasında geçiş yaparken akıcı animasyonlar. Gerçek Hayat Uygulamalarında Mikro Etkileşimler Aşağıda, mikro etkileşimlerin gerçek hayattaki uygulamalarını görebiliriz; bu ince animasyonlar, kullanıcı deneyimini geliştirmek için Flutter'da oluşturulmuştur. 👇🏽 Bu uygulamaların tasarım referansları Dribbble'dan alınmıştır. Flutter'da Aşamalı Animasyon Nasıl Oluşturulur Bu örnekte, sayfa kaydırıldığında bir sütun widget'ındaki alt öğeleri canlandıran kademeli bir animasyon oluşturacağız. Bu, açık animasyon yaklaşımı kullanılarak oluşturulacaktır çünkü bu durumda, animasyonun nasıl çalışmasını istediğimiz konusunda tam kontrole sahip olmamız gerekecektir. İşte tamamladığımızda animasyon nasıl görünecek 👇🏽 Önkoşul Bu eğitimden en iyi şekilde yararlanmak için aşağıdakilere sahip olmalısınız: Flutter'da animasyonların nasıl oluşturulduğuna dair temel anlayış Flutter ve Dart'ın temellerini iyi kavramak Bir kod düzenleyici, VScode veya Android Studio Üzerine inşa edilecek bir emülatör veya cihaz Projenin tüm önkoşullarını kontrol ettikten sonra hemen konuya girelim. Öncelikle bu iki sayfayı içeren ana ekranı oluşturacağız. Bu iki sayfa, kaydırma sırasında hangi sayfanın görüntüleneceğini kontrol eden bir sayfa görüntüleme widget'ına sarılacaktır. Ayrıca ana ekranın alt kısmında bize o anda bulunduğumuz sayfayı gösteren bir göstergemiz var. class MainScreen extends StatefulWidget { const MainScreen({super.key}); @override State<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen>{ final controller = PageController(keepPage: true); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: PageView( controller: controller, children: [ Page1(pageController: controller,), Page2(pageController: controller,), ], ), ), bottomNavigationBar: Container( alignment: Alignment.topCenter, padding: const EdgeInsets.only(top: 10), height: 30, child: SmoothPageIndicator( controller: controller, count: 2, effect: const JumpingDotEffect( dotHeight: 10, dotWidth: 10, activeDotColor: Colors.grey, dotColor: Colors.black12, ), ), ) ); } } Yukarıdaki kod parçacığında kullanılan adresinde bulabilirsiniz. Burada, geçerli sayfayı görünümde göstermek için kullanılır. Paketi bu şekilde ekleyebilirsiniz 👇🏽 SmoothPageIndicator pub.dev SmoothPageIndicator pubspec.yaml dependencies: flutter: sdk: flutter smooth_page_indicator: ^1.1.0 İki sayfada, birkaç boş kart widget'ı içeren bir sütun widget'ımız olacak. Bu boş kart widget'ları, kademeli animasyonu tam olarak görebilmemiz ve takdir edebilmemiz için sütunu doldurmak için kullanılacaktır. Boş kart widget'ını yeniden kullanılabilir bir bileşen olarak oluşturacağız, böylece onu kullandığımız her yerde sıfırdan oluşturmak zorunda kalmayacağız. class EmptyCard extends StatelessWidget { const EmptyCard({super.key, required this.width, required this.height}); final double width; final double height; @override Widget build(BuildContext context) { return Container( height: height, width: width, decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(10)), color: Colors.blue.shade200, boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 4, offset: Offset(0,4), ) ] ), ); } } Tüm bu ortak kodlar ortadan kalktıktan sonra artık animasyonu oluşturmaya başlayacağız. Öncelikle adını vereceğimiz yeni bir durum bilgisi olan widget oluşturacağız. Animasyonun nasıl çalışacağını kontrol etmek için çeşitli parametrelere sahip olacaktır. AnimateWidget class AnimateWidget extends StatefulWidget { const AnimateWidget({super.key, required this.duration, required this.position, required this.horizontalOffset, required this.child, required this.controller, }); final Duration duration; final int position; final double? horizontalOffset; final Widget child; final PageController controller; @override State<AnimateWidget> createState() => _AnimateWidgetState(); } Yukarıdaki kod parçasında görüldüğü gibi parametreleriyle başarılı bir şekilde kontrol edebiliriz: AnimateWidget Animasyonun süresi. , sayfa kaydırıldığında animasyonu tetiklemesine neden olur. pageController Animasyonlu öğenin yatay uzaklık miktarı. Daha sonra aşağıdakileri tanımlayacağız: AnimateWiget : Animasyon sırasını kontrol etmek için kullanılır. AnimationController Geçerli sayfa: geçerli sayfa verilerini tutmak için kullanılacaktır. Zamanlayıcı: Animasyonu bir miktar gecikmeden sonra tetiklemek için kullanılacaktır; Kademeli animasyon basamaklı efektini ortaya çıkaracak olan şey budur. class _AnimateWidgetState extends State<AnimateWidget> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin{ @override bool get wantKeepAlive => true; late AnimationController animationController; int currentPage = 0; Timer? _timer; @override void initState() { super.initState(); animationController = AnimationController(vsync: this, duration: widget.duration); _timer = Timer(getDelay(), animationController.forward); widget.controller.addListener(() { currentPage = widget.controller.page!.round(); if(currentPage == widget.controller.page){ _timer = Timer(getDelay(), animationController.forward); } }); } @override void dispose() { _timer?.cancel(); animationController.dispose(); super.dispose(); } Duration getDelay(){ var delayInMilliSec = widget.duration.inMilliseconds ~/ 6; int getStaggeredListDuration(){ return widget.position * delayInMilliSec; } return Duration(milliseconds: getStaggeredListDuration()); } Yukarıdaki kod parçacığını incelediğinizde şunu fark edeceksiniz: Animasyon sırasını kontrol etmek için kullandık, bu nedenle tanıttık. AnimationController SingleTickerProviderStateMixin yönteminde, başlattık, ardından kullanarak, sayfa girişinde ve sayfa kaydırmayı kullanarak animasyonu tetikledik. init state AnimationController animationController.forward Gecikmeye bağlı olarak animasyonun tetiklenmesini kontrol etmek için Zamanlayıcıyı kullandık. yönteminde kaynakları temizledik. dispose Widget'ın durumunu korumak için kullandık. Bunun amacı, bir sayfa kaydırıldığında ve artık görünür olmadığında öğesinin atılmasını önlemektir. AutomaticKeepAliveClientMixin AnimationController fonksiyonunda her bir element için animasyonun tetiklenmesinden önceki gecikmeyi hesapladık. Bunu başarmak için milisaniye cinsinden süreyi 6'ya bölüp sonuçları yaklaşıklaştırdık ve ardından konumla çarptık. Bu, öğenin konumudur (bu durumda dizin). getDelay Build yönteminde bir döndürüyoruz. Bu animasyonlu oluşturucuda, widget'ını döndüren adında bir widget işlevi döndüreceğiz. fonksiyonunda fonksiyonumuz var. AnimatedBuilder Transform.translate _slideAnimation _slideAnimation offsetAnimation işlevi, widget'ında kullanılan Animation özelliğini döndürür. widget'ı, animasyondaki değeri kullanarak alt widget'ı canlandırır. offsetAnimation Transform.translate Transform.translate @override Widget build(BuildContext context) { super.build(context); return AnimatedBuilder( animation: animationController, builder: (context, child){ return _slideAnimation(animationController); } ); } Widget _slideAnimation(Animation<double> animationController){ Animation<double> offsetAnimation(double offset, Animation<double> animationController) { return Tween<double>(begin: offset, end: 0.0).animate( CurvedAnimation( parent: animationController, curve: const Interval(0.0, 1.0, curve: Curves.ease), ), ); } return Transform.translate( offset: Offset( widget.horizontalOffset == 0.0 ? 0.0 : offsetAnimation(widget.horizontalOffset!, animationController).value, 0.0, ), child: widget.child, ); } Bu, Sınıfının tam kodudur 👇🏽 AnimateWidget class AnimateWidget extends StatefulWidget { const AnimateWidget({super.key, required this.duration, required this.position, required this.horizontalOffset, required this.child, required this.controller, }); final Duration duration; final int position; final double? horizontalOffset; final Widget child; final PageController controller; @override State<AnimateWidget> createState() => _AnimateWidgetState(); } class _AnimateWidgetState extends State<AnimateWidget> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin{ @override bool get wantKeepAlive => true; late AnimationController animationController; int currentPage = 0; Timer? _timer; @override void initState() { super.initState(); animationController = AnimationController(vsync: this, duration: widget.duration); _timer = Timer(getDelay(), animationController.forward); widget.controller.addListener(() { currentPage = widget.controller.page!.round(); if(currentPage == widget.controller.page){ _timer = Timer(getDelay(), animationController.forward); } }); } @override void dispose() { _timer?.cancel(); animationController.dispose(); super.dispose(); } Duration getDelay(){ var delayInMilliSec = widget.duration.inMilliseconds ~/ 6; int getStaggeredListDuration(){ return widget.position * delayInMilliSec; } return Duration(milliseconds: getStaggeredListDuration()); } @override Widget build(BuildContext context) { super.build(context); return AnimatedBuilder( animation: animationController, builder: (context, child){ return _slideAnimation(animationController); } ); } Widget _slideAnimation(Animation<double> animationController){ Animation<double> offsetAnimation(double offset, Animation<double> animationController) { return Tween<double>(begin: offset, end: 0.0).animate( CurvedAnimation( parent: animationController, curve: const Interval(0.0, 1.0, curve: Curves.ease), ), ); } return Transform.translate( offset: Offset( widget.horizontalOffset == 0.0 ? 0.0 : offsetAnimation(widget.horizontalOffset!, animationController).value, 0.0, ), child: widget.child, ); } } Daha sonra, bu sınıfını bir sütun widget'ında kullanmak için, bir liste döndüren adında statik bir yönteme sahip bir sınıf oluşturacağız. Bu yöntemde, bir listesi de dahil olmak üzere gerekli tüm parametreleri iletiyoruz. parametresi, canlandıracağımız öğelerin listesini ileteceğimiz yerdir. AnimateWidget toStaggeredList children children Daha sonra, her çocuğu ile sararak çocukları haritalandıracağız. AnimateWidget class AnimateList{ static List<Widget>toStaggeredList({ required Duration duration, double? horizontalOffset, required PageController controller, required List<Widget>children, })=> children .asMap() .map((index, widget){ return MapEntry( index, AnimateWidget( duration: duration, position: index, horizontalOffset: horizontalOffset, controller: controller, child: widget, ) ); }) .values .toList(); } , listedeki her alt öğeyi başarılı bir şekilde canlandırmak için gerekli parametreleri iletiyoruz. yöntemini kullanarak artık üzerinde çalıştığımız iki sayfaya uygulayabiliriz. AnimateWidget AnimateList.toStaggeredList class Page1 extends StatelessWidget { const Page1({super.key, required this.pageController}); final PageController pageController; @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( children: AnimateList.toStaggeredList( duration: const Duration(milliseconds: 375), controller: pageController, horizontalOffset: MediaQuery.of(context).size.width / 2, children: [ const EmptyCard(width: 250, height: 50,), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 180, height: 80,), ), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 270, height: 50,), ), const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ EmptyCard(height: 50, width: 70), EmptyCard(height: 50, width: 70), EmptyCard(height: 50, width: 70), ], ), ), const EmptyCard(width: 250, height: 50,), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 180, height: 80,), ), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 270, height: 50,), ), const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ EmptyCard(height: 50, width: 70), EmptyCard(height: 50, width: 70), EmptyCard(height: 50, width: 70), ], ), ), ], ), ), ); } } class Page2 extends StatelessWidget { const Page2({super.key, required this.pageController}); final PageController pageController; @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( children: AnimateList.toStaggeredList( duration: const Duration(milliseconds: 375), controller: pageController, horizontalOffset: MediaQuery.of(context).size.width / 2, children: [ const EmptyCard(width: 220, height: 70,), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 300, height: 70,), ), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 200, height: 50,), ), const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ EmptyCard(height: 70, width: 70), EmptyCard(height: 70, width: 70), ], ), ), const EmptyCard(width: 220, height: 70,), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 300, height: 70,), ), const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ EmptyCard(height: 70, width: 70), EmptyCard(height: 70, width: 70), ], ), ), ], ), ), ); } } Sütun widget'ının alt öğelerinde ileteceğiz ve sütunda görüntülenecek widget'lar da dahil olmak üzere gerekli parametreleri ileteceğiz. Bununla, kaydırmayla tetiklenen kademeli bir animasyonu başarıyla oluşturduk. Kodun tamamını kontrol edebilirsiniz. AnimateList.toStaggeredList buradan Bu bizim nihai sonucumuz: Çözüm Bu eğitimimizin sonuna geldik. Bu noktada mikro etkileşim kavramına ve kullanıcı deneyimine etkisine değindik. Ayrıca, sayfa kaydırıldığında tetiklenen bir sütun widget'ındaki öğelerin aşamalı animasyonunu oluşturma sürecinden de geçtik. Projenizin kullanıcı deneyimini geliştirmek için uygulamanıza ekleyebileceğiniz pek çok türde mikro etkileşim vardır; Flutter'da daha etkileşimli animasyonlar oluşturarak denemeler yapabilirsiniz. Bu makaleyi faydalı bulduysanız beğenerek veya yorum yaparak destek olabilirsiniz. Daha fazla ilgili yazılarım için beni takip edebilirsiniz. Referanslar Flutter Kademeli Animasyon Paketi