एक कंपित एनीमेशन में अनुक्रमिक या ओवरलैपिंग एनिमेशन होते हैं। एनीमेशन पूरी तरह से अनुक्रमिक हो सकता है, जिसमें एक के बाद एक बदलाव होते हैं, या यह आंशिक रूप से या पूरी तरह से ओवरलैप हो सकता है। जब एनीमेशन अनुक्रमिक होता है, तो तत्वों को प्रत्येक तत्व के प्रारंभ समय के बीच थोड़ी देरी के साथ क्रमिक रूप से एनिमेटेड किया जाता है। यह एक कैस्केडिंग या लहर प्रभाव बनाता है, जहां एनीमेशन एक साथ सभी तत्वों के बजाय चरणों में चलता हुआ प्रतीत होता है।
स्टैगर्ड एनिमेशन को माइक्रो-इंटरैक्शन का एक प्रकार माना जाता है क्योंकि वे उपयोगकर्ता को इंटरफ़ेस के माध्यम से मार्गदर्शन करने वाले सूक्ष्म, इंटरैक्टिव फ़ीडबैक प्रदान करके उपयोगकर्ता अनुभव को बढ़ाते हैं। फ़्लटर में, आप अंतर्निहित या स्पष्ट एनीमेशन का उपयोग करके सूक्ष्म एनिमेशन तैयार करके माइक्रो-इंटरैक्शन बना सकते हैं।
संदर्भ के लिए, निहित एनिमेशन को सरल और उपयोग में आसान बनाने के लिए डिज़ाइन किया गया है क्योंकि एनिमेशन विवरण को सारगर्भित किया जाता है जबकि स्पष्ट एनिमेशन जटिल एनिमेशन के लिए अधिक उपयुक्त होते हैं क्योंकि वे एनिमेशन प्रक्रिया पर पूर्ण नियंत्रण प्रदान करते हैं। इसका उपयोग विशेष रूप से तब किया जाता है जब आपको एनिमेशन पर अधिक बारीक नियंत्रण की आवश्यकता होती है।
इस लेख में, हम माइक्रो-इंटरैक्शन की अवधारणा पर गहराई से चर्चा करेंगे, फिर एक माइक्रो-इंटरैक्शन उपयोग मामले के लिए, हम एक स्तंभित एनीमेशन बनाने के लिए स्पष्ट एनीमेशन का उपयोग करेंगे जो एक कॉलम विजेट में निहित बच्चों को एनिमेट करता है।
बेहतरीन उत्पाद वे उत्पाद हैं जो सुविधाओं और विवरण दोनों पर अच्छी तरह से काम करते हैं। सुविधाएँ लोगों को आपके उत्पादों तक लाती हैं, लेकिन विवरण उन्हें बनाए रखते हैं। ये विवरण ऐसी चीजें हैं जो आपके ऐप को दूसरों से अलग बनाती हैं। माइक्रो-इंटरैक्शन के साथ, आप अपने उपयोगकर्ताओं को सुखद प्रतिक्रिया देकर ये विवरण बना सकते हैं।
माइक्रो-इंटरैक्शन की अवधारणा इस विचार पर आधारित है कि छोटे विवरण समग्र उपयोगकर्ता अनुभव पर बड़ा प्रभाव डाल सकते हैं। माइक्रो-इंटरैक्शन का उपयोग फीडबैक या किसी कार्रवाई के परिणाम को संप्रेषित करने जैसे आवश्यक कार्यों को पूरा करने के लिए किया जा सकता है। माइक्रो-इंटरैक्शन के उदाहरणों में शामिल हैं:
नीचे, हम माइक्रो-इंटरैक्शन के वास्तविक जीवन के अनुप्रयोगों को देख सकते हैं, ये सूक्ष्म एनिमेशन उपयोगकर्ता अनुभव को बढ़ाने के लिए फ़्लटर में बनाए गए हैं।
इन ऐप्स के लिए डिज़ाइन संदर्भ ड्रिब्बल से प्राप्त किए गए थे
इस उदाहरण में, हम एक ऐसा कंपित एनीमेशन बनाएंगे जो पेज स्वाइप करने पर कॉलम विजेट में बच्चों को एनिमेट करता है। इसे स्पष्ट एनीमेशन दृष्टिकोण का उपयोग करके बनाया जाएगा क्योंकि इस मामले में, हमें इस बात पर पूरा नियंत्रण रखना होगा कि हम एनीमेशन को कैसे चलाना चाहते हैं।
जब हम पूरा कर लेंगे तो एनीमेशन कुछ इस तरह दिखेगा 👇🏽
इस ट्यूटोरियल का अधिकतम लाभ उठाने के लिए आपके पास निम्नलिखित चीजें होनी चाहिए:
एक बार जब आप परियोजना की सभी पूर्व-आवश्यकताओं की जांच कर लें, तो चलिए इसमें आगे बढ़ते हैं।
सबसे पहले, हम मुख्य स्क्रीन बनाएंगे जिसमें ये दो पेज होंगे। ये दोनों पेज एक पेजव्यू विजेट में लिपटे होंगे जो नियंत्रित करेगा कि स्वाइप करने पर कौन सा पेज प्रदर्शित होगा। साथ ही, मुख्य स्क्रीन के निचले भाग में, हमारे पास एक संकेतक है जो हमें वह पेज दिखाता है जिस पर हम वर्तमान में हैं।
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
पास करेंगे और आवश्यक पैरामीटर पास करेंगे, जिसमें कॉलम में प्रदर्शित किए जाने वाले विजेट शामिल हैं। इसके साथ, हमने स्वाइप पर ट्रिगर होने वाला एक स्टैगर्ड एनीमेशन सफलतापूर्वक बनाया है। आप पूरा कोड यहाँ देख सकते हैं।
यह हमारा अंतिम परिणाम है:
हम इस ट्यूटोरियल के अंत में आ गए हैं। इस बिंदु पर, हमने माइक्रो-इंटरैक्शन की अवधारणा और उपयोगकर्ता अनुभव पर इसके प्रभाव को कवर किया। हमने पेज स्वाइप पर ट्रिगर किए गए कॉलम विजेट में आइटम के कंपित एनीमेशन बनाने की प्रक्रिया को भी देखा।
आप अपने प्रोजेक्ट के उपयोगकर्ता अनुभव को बेहतर बनाने के लिए अपने ऐप में कई प्रकार के माइक्रो-इंटरैक्शन जोड़ सकते हैं; आप फ़्लटर में अधिक इंटरैक्टिव एनिमेशन बनाकर प्रयोग कर सकते हैं।
अगर आपको यह लेख उपयोगी लगा, तो आप इसे लाइक या कमेंट करके अपना समर्थन दे सकते हैं। आप और भी संबंधित लेखों के लिए मुझे फॉलो भी कर सकते हैं।