paint-brush
फ़्लटर में स्टैगर्ड एनीमेशन बनाना: एक माइक्रो-इंटरैक्शन गाइडद्वारा@nikkieke
460 रीडिंग
460 रीडिंग

फ़्लटर में स्टैगर्ड एनीमेशन बनाना: एक माइक्रो-इंटरैक्शन गाइड

द्वारा 19m2024/06/04
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

माइक्रो-इंटरैक्शन की अवधारणा इस विचार पर आधारित है कि छोटे विवरण समग्र उपयोगकर्ता अनुभव पर बड़ा प्रभाव डाल सकते हैं। फ़्लटर में, आप अंतर्निहित या स्पष्ट एनीमेशन का उपयोग करके सूक्ष्म एनिमेशन तैयार करके माइक्रो-इंटरैक्शन बना सकते हैं। इस लेख में, हम एक कंपित एनीमेशन बनाएंगे जो उस पृष्ठ को स्वाइप करने पर कॉलम विजेट में बच्चों को एनिमेट करता है।
featured image - फ़्लटर में स्टैगर्ड एनीमेशन बनाना: एक माइक्रो-इंटरैक्शन गाइड
undefined HackerNoon profile picture
0-item

एक कंपित एनीमेशन में अनुक्रमिक या ओवरलैपिंग एनिमेशन होते हैं। एनीमेशन पूरी तरह से अनुक्रमिक हो सकता है, जिसमें एक के बाद एक बदलाव होते हैं, या यह आंशिक रूप से या पूरी तरह से ओवरलैप हो सकता है। जब एनीमेशन अनुक्रमिक होता है, तो तत्वों को प्रत्येक तत्व के प्रारंभ समय के बीच थोड़ी देरी के साथ क्रमिक रूप से एनिमेटेड किया जाता है। यह एक कैस्केडिंग या लहर प्रभाव बनाता है, जहां एनीमेशन एक साथ सभी तत्वों के बजाय चरणों में चलता हुआ प्रतीत होता है।


स्टैगर्ड एनिमेशन को माइक्रो-इंटरैक्शन का एक प्रकार माना जाता है क्योंकि वे उपयोगकर्ता को इंटरफ़ेस के माध्यम से मार्गदर्शन करने वाले सूक्ष्म, इंटरैक्टिव फ़ीडबैक प्रदान करके उपयोगकर्ता अनुभव को बढ़ाते हैं। फ़्लटर में, आप अंतर्निहित या स्पष्ट एनीमेशन का उपयोग करके सूक्ष्म एनिमेशन तैयार करके माइक्रो-इंटरैक्शन बना सकते हैं।


संदर्भ के लिए, निहित एनिमेशन को सरल और उपयोग में आसान बनाने के लिए डिज़ाइन किया गया है क्योंकि एनिमेशन विवरण को सारगर्भित किया जाता है जबकि स्पष्ट एनिमेशन जटिल एनिमेशन के लिए अधिक उपयुक्त होते हैं क्योंकि वे एनिमेशन प्रक्रिया पर पूर्ण नियंत्रण प्रदान करते हैं। इसका उपयोग विशेष रूप से तब किया जाता है जब आपको एनिमेशन पर अधिक बारीक नियंत्रण की आवश्यकता होती है।


इस लेख में, हम माइक्रो-इंटरैक्शन की अवधारणा पर गहराई से चर्चा करेंगे, फिर एक माइक्रो-इंटरैक्शन उपयोग मामले के लिए, हम एक स्तंभित एनीमेशन बनाने के लिए स्पष्ट एनीमेशन का उपयोग करेंगे जो एक कॉलम विजेट में निहित बच्चों को एनिमेट करता है।

माइक्रो-इंटरैक्शन की अवधारणा क्या है?

बेहतरीन उत्पाद वे उत्पाद हैं जो सुविधाओं और विवरण दोनों पर अच्छी तरह से काम करते हैं। सुविधाएँ लोगों को आपके उत्पादों तक लाती हैं, लेकिन विवरण उन्हें बनाए रखते हैं। ये विवरण ऐसी चीजें हैं जो आपके ऐप को दूसरों से अलग बनाती हैं। माइक्रो-इंटरैक्शन के साथ, आप अपने उपयोगकर्ताओं को सुखद प्रतिक्रिया देकर ये विवरण बना सकते हैं।


माइक्रो-इंटरैक्शन की अवधारणा इस विचार पर आधारित है कि छोटे विवरण समग्र उपयोगकर्ता अनुभव पर बड़ा प्रभाव डाल सकते हैं। माइक्रो-इंटरैक्शन का उपयोग फीडबैक या किसी कार्रवाई के परिणाम को संप्रेषित करने जैसे आवश्यक कार्यों को पूरा करने के लिए किया जा सकता है। माइक्रो-इंटरैक्शन के उदाहरणों में शामिल हैं:


  • बटन एनिमेशन: बटन पर माउस घुमाने या दबाने पर उसका रंग या आकार बदल जाता है।


  • लोडिंग संकेतक: एनिमेशन जो उपयोगकर्ता को संकेत देते हैं कि कोई प्रक्रिया प्रगति पर है।


  • स्वाइप जेस्चर: एनिमेशन जो स्वाइप जेस्चर पर प्रतिक्रिया देते हैं।


  • नेविगेशन संक्रमण: स्क्रीन के बीच संक्रमण करते समय सहज एनिमेशन।

वास्तविक जीवन के अनुप्रयोगों में सूक्ष्म-अंतःक्रियाएँ

नीचे, हम माइक्रो-इंटरैक्शन के वास्तविक जीवन के अनुप्रयोगों को देख सकते हैं, ये सूक्ष्म एनिमेशन उपयोगकर्ता अनुभव को बढ़ाने के लिए फ़्लटर में बनाए गए हैं।

इन ऐप्स के लिए डिज़ाइन संदर्भ ड्रिब्बल से प्राप्त किए गए थे

फ़्लटर में स्टैगर्ड एनीमेशन कैसे बनाएं

इस उदाहरण में, हम एक ऐसा कंपित एनीमेशन बनाएंगे जो पेज स्वाइप करने पर कॉलम विजेट में बच्चों को एनिमेट करता है। इसे स्पष्ट एनीमेशन दृष्टिकोण का उपयोग करके बनाया जाएगा क्योंकि इस मामले में, हमें इस बात पर पूरा नियंत्रण रखना होगा कि हम एनीमेशन को कैसे चलाना चाहते हैं।


जब हम पूरा कर लेंगे तो एनीमेशन कुछ इस तरह दिखेगा 👇🏽

शर्त

इस ट्यूटोरियल का अधिकतम लाभ उठाने के लिए आपके पास निम्नलिखित चीजें होनी चाहिए:


  • फ़्लटर में एनिमेशन कैसे बनाए जाते हैं, इसकी बुनियादी समझ
  • फ़्लटर और डार्ट के मूल सिद्धांतों की अच्छी समझ
  • एक कोड संपादक, या तो VScode या Android Studio
  • एक एमुलेटर या डिवाइस जिस पर निर्माण किया जा सके


एक बार जब आप परियोजना की सभी पूर्व-आवश्यकताओं की जांच कर लें, तो चलिए इसमें आगे बढ़ते हैं।


सबसे पहले, हम मुख्य स्क्रीन बनाएंगे जिसमें ये दो पेज होंगे। ये दोनों पेज एक पेजव्यू विजेट में लिपटे होंगे जो नियंत्रित करेगा कि स्वाइप करने पर कौन सा पेज प्रदर्शित होगा। साथ ही, मुख्य स्क्रीन के निचले भाग में, हमारे पास एक संकेतक है जो हमें वह पेज दिखाता है जिस पर हम वर्तमान में हैं।

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


ऊपर दिए गए कोड स्निपेट में इस्तेमाल किया गया SmoothPageIndicator pub.dev पर पाया जा सकता है। यहाँ, SmoothPageIndicator उपयोग वर्तमान पृष्ठ को दृश्य में दिखाने के लिए किया जाता है। आप इस पैकेज को अपने pubspec.yaml में इस तरह जोड़ सकते हैं 👇🏽

 dependencies: flutter: sdk: flutter smooth_page_indicator: ^1.1.0


दो पेजों में, हमारे पास कई खाली कार्ड विजेट के साथ एक कॉलम विजेट होगा। इन खाली कार्ड विजेट का उपयोग कॉलम को पॉप्युलेट करने के लिए किया जाएगा ताकि हम स्टैगर्ड एनीमेशन को पूरी तरह से देख सकें और उसकी सराहना कर सकें। हम खाली कार्ड विजेट को एक पुनः उपयोग करने योग्य घटक के रूप में बनाएंगे ताकि हमें इसे हर जगह पर स्क्रैच से न बनाना पड़े।

 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), ) ] ), ); } }


इन सभी बॉयलरप्लेट कोड को पूरा करने के बाद, अब हम एनीमेशन बनाना शुरू करेंगे। सबसे पहले, हम एक नया स्टेटफुल विजेट बनाएंगे जिसे हम 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(); }


जैसा कि ऊपर दिए गए स्निपेट में देखा गया है, AnimateWidget के पैरामीटर्स के साथ, हम सफलतापूर्वक नियंत्रित कर सकते हैं:

  • एनीमेशन की अवधि.
  • pageController , जिसके कारण पृष्ठ स्वाइप किए जाने पर एनीमेशन ट्रिगर हो जाता है।
  • एनिमेटेड तत्व की क्षैतिज ऑफसेट की मात्रा.


इसके बाद, AnimateWiget में, हम निम्नलिखित को परिभाषित करेंगे:

  • AnimationController : एनीमेशन अनुक्रम को नियंत्रित करने के लिए उपयोग किया जाता है।

  • वर्तमान पृष्ठ: वर्तमान पृष्ठ डेटा रखने के लिए उपयोग किया जाएगा.

  • टाइमर: कुछ देरी के बाद एनीमेशन को ट्रिगर करने के लिए उपयोग किया जाएगा; यह वही है जो कंपित एनीमेशन कैस्केडिंग प्रभाव लाएगा।


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

उपरोक्त कोड स्निपेट को तोड़कर देखने पर आप देखेंगे कि:

  • हमने एनीमेशन अनुक्रम को नियंत्रित करने के लिए AnimationController उपयोग किया, इस वजह से, हमने SingleTickerProviderStateMixin पेश किया।


  • init state विधि में, हमने AnimationController आरंभीकृत किया, फिर पेज प्रविष्टि और पेज स्वाइप पर animationController.forward का उपयोग करके एनीमेशन को ट्रिगर किया।


  • हमने विलंब के आधार पर एनीमेशन के ट्रिगरिंग को नियंत्रित करने के लिए टाइमर का उपयोग किया।


  • dispose विधि में, हमने संसाधनों को साफ किया।


  • हमने विजेट की स्थिति को सुरक्षित रखने के लिए AutomaticKeepAliveClientMixin का उपयोग किया। यह AnimationController के निपटान को रोकने के लिए है, जब कोई पेज स्वाइप किया जाता है और दिखाई नहीं देता है।


  • getDelay फ़ंक्शन में, हमने प्रत्येक तत्व के लिए एनीमेशन ट्रिगर होने से पहले की देरी की गणना की। इसे प्राप्त करने के लिए, हमने मिलीसेकंड में अवधि को 6 से विभाजित किया और परिणामों का अनुमान लगाया, फिर इसे स्थिति से गुणा किया। यह तत्व की स्थिति (इस मामले में, सूचकांक) है।


बिल्ड विधि में, हम एक AnimatedBuilder लौटाते हैं। इस एनिमेटेड बिल्डर में, हम _slideAnimation नामक एक विजेट फ़ंक्शन लौटाएंगे जो एक Transform.translate विजेट लौटाता है। _slideAnimation फ़ंक्शन में, हमारे पास offsetAnimation फ़ंक्शन है।


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


यह 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, ); } }

इसके बाद, कॉलम विजेट में इस AnimateWidget क्लास का उपयोग करने के लिए, हम toStaggeredList नामक एक स्थिर विधि के साथ एक क्लास बनाएंगे जो एक सूची लौटाता है, इस विधि में, हम सभी आवश्यक पैरामीटर पास करते हैं, जिसमें एक सूची children भी शामिल है। children पैरामीटर वह जगह है जहाँ हम उन तत्वों की सूची पास करेंगे जिन्हें हम एनिमेट करेंगे।


इसके बाद, हम बच्चों को मैप करेंगे, प्रत्येक बच्चे को 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(); }


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), ], ), ), ], ), ), ); } }

कॉलम विजेट के बच्चों में, हम AnimateList.toStaggeredList पास करेंगे और आवश्यक पैरामीटर पास करेंगे, जिसमें कॉलम में प्रदर्शित किए जाने वाले विजेट शामिल हैं। इसके साथ, हमने स्वाइप पर ट्रिगर होने वाला एक स्टैगर्ड एनीमेशन सफलतापूर्वक बनाया है। आप पूरा कोड यहाँ देख सकते हैं।


यह हमारा अंतिम परिणाम है:

निष्कर्ष

हम इस ट्यूटोरियल के अंत में आ गए हैं। इस बिंदु पर, हमने माइक्रो-इंटरैक्शन की अवधारणा और उपयोगकर्ता अनुभव पर इसके प्रभाव को कवर किया। हमने पेज स्वाइप पर ट्रिगर किए गए कॉलम विजेट में आइटम के कंपित एनीमेशन बनाने की प्रक्रिया को भी देखा।


आप अपने प्रोजेक्ट के उपयोगकर्ता अनुभव को बेहतर बनाने के लिए अपने ऐप में कई प्रकार के माइक्रो-इंटरैक्शन जोड़ सकते हैं; आप फ़्लटर में अधिक इंटरैक्टिव एनिमेशन बनाकर प्रयोग कर सकते हैं।


अगर आपको यह लेख उपयोगी लगा, तो आप इसे लाइक या कमेंट करके अपना समर्थन दे सकते हैं। आप और भी संबंधित लेखों के लिए मुझे फॉलो भी कर सकते हैं।

संदर्भ

स्पंदन स्टैगर्ड एनीमेशन पैकेज