paint-brush
Flutter'da Aşamalı Animasyon Oluşturma: Mikro Etkileşimler Kılavuzuile@nikkieke
442 okumalar
442 okumalar

Flutter'da Aşamalı Animasyon Oluşturma: Mikro Etkileşimler Kılavuzu

ile 19m2024/06/04
Read on Terminal Reader

Çok uzun; Okumak

Mikro etkileşim kavramı, küçük ayrıntıların genel kullanıcı deneyimi üzerinde büyük etkiye sahip olabileceği fikrine dayanmaktadır. Flutter'da, örtülü veya açık animasyonu kullanarak incelikli animasyonlar hazırlayarak mikro Etkileşimler oluşturabilirsiniz. Bu yazıda, bir sütun widget'ındaki çocukları sayfa kaydırıldığında hareketlendiren kademeli bir animasyon oluşturacağız.
featured image - Flutter'da Aşamalı Animasyon Oluşturma: Mikro Etkileşimler Kılavuzu
undefined HackerNoon profile picture
0-item

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 SmoothPageIndicator pub.dev adresinde bulabilirsiniz. Burada, SmoothPageIndicator geçerli sayfayı görünümde göstermek için kullanılır. Paketi pubspec.yaml bu şekilde ekleyebilirsiniz 👇🏽

 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 AnimateWidget 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.

 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 AnimateWidget parametreleriyle başarılı bir şekilde kontrol edebiliriz:

  • Animasyonun süresi.
  • pageController , sayfa kaydırıldığında animasyonu tetiklemesine neden olur.
  • Animasyonlu öğenin yatay uzaklık miktarı.


Daha sonra AnimateWiget aşağıdakileri tanımlayacağız:

  • AnimationController : Animasyon sırasını kontrol etmek için kullanılır.

  • 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 AnimationController kullandık, bu nedenle SingleTickerProviderStateMixin tanıttık.


  • init state yönteminde, AnimationController başlattık, ardından animationController.forward kullanarak, sayfa girişinde ve sayfa kaydırmayı kullanarak animasyonu tetikledik.


  • Gecikmeye bağlı olarak animasyonun tetiklenmesini kontrol etmek için Zamanlayıcıyı kullandık.


  • dispose yönteminde kaynakları temizledik.


  • Widget'ın durumunu korumak için AutomaticKeepAliveClientMixin kullandık. Bunun amacı, bir sayfa kaydırıldığında ve artık görünür olmadığında AnimationController öğesinin atılmasını önlemektir.


  • getDelay 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).


Build yönteminde bir AnimatedBuilder döndürüyoruz. Bu animasyonlu oluşturucuda, Transform.translate widget'ını döndüren _slideAnimation adında bir widget işlevi döndüreceğiz. _slideAnimation fonksiyonunda offsetAnimation fonksiyonumuz var.


offsetAnimation işlevi, Transform.translate widget'ında kullanılan Animation özelliğini döndürür. Transform.translate widget'ı, animasyondaki değeri kullanarak alt widget'ı canlandırır.

 @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, AnimateWidget Sınıfının tam kodudur 👇🏽

 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 AnimateWidget sınıfını bir sütun widget'ında kullanmak için, bir liste döndüren toStaggeredList adında statik bir yönteme sahip bir sınıf oluşturacağız. Bu yöntemde, bir children listesi de dahil olmak üzere gerekli tüm parametreleri iletiyoruz. children parametresi, canlandıracağımız öğelerin listesini ileteceğimiz yerdir.


Daha sonra, her çocuğu AnimateWidget ile sararak çocukları haritalandıracağız.

 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(); }


AnimateWidget , listedeki her alt öğeyi başarılı bir şekilde canlandırmak için gerekli parametreleri iletiyoruz. AnimateList.toStaggeredList yöntemini kullanarak artık üzerinde çalıştığımız iki sayfaya uygulayabiliriz.

 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 AnimateList.toStaggeredList 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ı buradan kontrol edebilirsiniz.


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